Bug: feat(opensearch): add amount and customer_id as filters and handle name for different indexes



Add amount as a filter / category for global search.
Also, add customer_id as a filter for searching based on customer_id on Customers page.

diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs
index e85340e49d2..f12198d0054 100644
--- a/crates/analytics/src/opensearch.rs
+++ b/crates/analytics/src/opensearch.rs
@@ -456,7 +456,7 @@ pub struct OpenSearchQueryBuilder {
     pub query: String,
     pub offset: Option<i64>,
     pub count: Option<i64>,
-    pub filters: Vec<(String, Vec<String>)>,
+    pub filters: Vec<(String, Vec<Value>)>,
     pub time_range: Option<OpensearchTimeRange>,
     search_params: Vec<AuthInfo>,
     case_sensitive_fields: HashSet<&'static str>,
@@ -477,6 +477,8 @@ impl OpenSearchQueryBuilder {
                 "search_tags.keyword",
                 "card_last_4.keyword",
                 "payment_id.keyword",
+                "amount",
+                "customer_id.keyword",
             ]),
         }
     }
@@ -492,7 +494,7 @@ impl OpenSearchQueryBuilder {
         Ok(())
     }
 
-    pub fn add_filter_clause(&mut self, lhs: String, rhs: Vec<String>) -> QueryResult<()> {
+    pub fn add_filter_clause(&mut self, lhs: String, rhs: Vec<Value>) -> QueryResult<()> {
         self.filters.push((lhs, rhs));
         Ok(())
     }
@@ -505,9 +507,18 @@ impl OpenSearchQueryBuilder {
         }
     }
 
+    pub fn get_amount_field(&self, index: SearchIndex) -> &str {
+        match index {
+            SearchIndex::Refunds | SearchIndex::SessionizerRefunds => "refund_amount",
+            SearchIndex::Disputes | SearchIndex::SessionizerDisputes => "dispute_amount",
+            _ => "amount",
+        }
+    }
+
     pub fn build_filter_array(
         &self,
-        case_sensitive_filters: Vec<&(String, Vec<String>)>,
+        case_sensitive_filters: Vec<&(String, Vec<Value>)>,
+        index: SearchIndex,
     ) -> Vec<Value> {
         let mut filter_array = Vec::new();
         if !self.query.is_empty() {
@@ -522,7 +533,14 @@ impl OpenSearchQueryBuilder {
 
         let case_sensitive_json_filters = case_sensitive_filters
             .into_iter()
-            .map(|(k, v)| json!({"terms": {k: v}}))
+            .map(|(k, v)| {
+                let key = if *k == "amount" {
+                    self.get_amount_field(index).to_string()
+                } else {
+                    k.clone()
+                };
+                json!({"terms": {key: v}})
+            })
             .collect::<Vec<Value>>();
 
         filter_array.extend(case_sensitive_json_filters);
@@ -542,7 +560,7 @@ impl OpenSearchQueryBuilder {
     pub fn build_case_insensitive_filters(
         &self,
         mut payload: Value,
-        case_insensitive_filters: &[&(String, Vec<String>)],
+        case_insensitive_filters: &[&(String, Vec<Value>)],
         auth_array: Vec<Value>,
         index: SearchIndex,
     ) -> Value {
@@ -690,22 +708,16 @@ impl OpenSearchQueryBuilder {
     /// Ensure that the input data and the structure of the query are valid and correctly handled.
     pub fn construct_payload(&self, indexes: &[SearchIndex]) -> QueryResult<Vec<Value>> {
         let mut query_obj = Map::new();
-        let mut bool_obj = Map::new();
+        let bool_obj = Map::new();
 
         let (case_sensitive_filters, case_insensitive_filters): (Vec<_>, Vec<_>) = self
             .filters
             .iter()
             .partition(|(k, _)| self.case_sensitive_fields.contains(k.as_str()));
 
-        let filter_array = self.build_filter_array(case_sensitive_filters);
-
-        if !filter_array.is_empty() {
-            bool_obj.insert("filter".to_string(), Value::Array(filter_array));
-        }
-
         let should_array = self.build_auth_array();
 
-        query_obj.insert("bool".to_string(), Value::Object(bool_obj));
+        query_obj.insert("bool".to_string(), Value::Object(bool_obj.clone()));
 
         let mut sort_obj = Map::new();
         sort_obj.insert(
@@ -724,6 +736,16 @@ impl OpenSearchQueryBuilder {
                         Value::Object(sort_obj.clone())
                     ]
                 });
+                let filter_array = self.build_filter_array(case_sensitive_filters.clone(), *index);
+                if !filter_array.is_empty() {
+                    payload
+                        .get_mut("query")
+                        .and_then(|query| query.get_mut("bool"))
+                        .and_then(|bool_obj| bool_obj.as_object_mut())
+                        .map(|bool_map| {
+                            bool_map.insert("filter".to_string(), Value::Array(filter_array));
+                        });
+                }
                 payload = self.build_case_insensitive_filters(
                     payload,
                     &case_insensitive_filters,
diff --git a/crates/analytics/src/search.rs b/crates/analytics/src/search.rs
index 9be0200030d..f3bda521c5b 100644
--- a/crates/analytics/src/search.rs
+++ b/crates/analytics/src/search.rs
@@ -5,12 +5,17 @@ use api_models::analytics::search::{
 use common_utils::errors::{CustomResult, ReportSwitchExt};
 use error_stack::ResultExt;
 use router_env::tracing;
+use serde_json::Value;
 
 use crate::{
     enums::AuthInfo,
     opensearch::{OpenSearchClient, OpenSearchError, OpenSearchQuery, OpenSearchQueryBuilder},
 };
 
+pub fn convert_to_value<T: Into<Value>>(items: Vec<T>) -> Vec<Value> {
+    items.into_iter().map(|item| item.into()).collect()
+}
+
 pub async fn msearch_results(
     client: &OpenSearchClient,
     req: GetGlobalSearchRequest,
@@ -38,21 +43,24 @@ pub async fn msearch_results(
         if let Some(currency) = filters.currency {
             if !currency.is_empty() {
                 query_builder
-                    .add_filter_clause("currency.keyword".to_string(), currency.clone())
+                    .add_filter_clause("currency.keyword".to_string(), convert_to_value(currency))
                     .switch()?;
             }
         };
         if let Some(status) = filters.status {
             if !status.is_empty() {
                 query_builder
-                    .add_filter_clause("status.keyword".to_string(), status.clone())
+                    .add_filter_clause("status.keyword".to_string(), convert_to_value(status))
                     .switch()?;
             }
         };
         if let Some(payment_method) = filters.payment_method {
             if !payment_method.is_empty() {
                 query_builder
-                    .add_filter_clause("payment_method.keyword".to_string(), payment_method.clone())
+                    .add_filter_clause(
+                        "payment_method.keyword".to_string(),
+                        convert_to_value(payment_method),
+                    )
                     .switch()?;
             }
         };
@@ -61,15 +69,17 @@ pub async fn msearch_results(
                 query_builder
                     .add_filter_clause(
                         "customer_email.keyword".to_string(),
-                        customer_email
-                            .iter()
-                            .filter_map(|email| {
-                                // TODO: Add trait based inputs instead of converting this to strings
-                                serde_json::to_value(email)
-                                    .ok()
-                                    .and_then(|a| a.as_str().map(|a| a.to_string()))
-                            })
-                            .collect(),
+                        convert_to_value(
+                            customer_email
+                                .iter()
+                                .filter_map(|email| {
+                                    // TODO: Add trait based inputs instead of converting this to strings
+                                    serde_json::to_value(email)
+                                        .ok()
+                                        .and_then(|a| a.as_str().map(|a| a.to_string()))
+                                })
+                                .collect(),
+                        ),
                     )
                     .switch()?;
             }
@@ -79,15 +89,17 @@ pub async fn msearch_results(
                 query_builder
                     .add_filter_clause(
                         "feature_metadata.search_tags.keyword".to_string(),
-                        search_tags
-                            .iter()
-                            .filter_map(|search_tag| {
-                                // TODO: Add trait based inputs instead of converting this to strings
-                                serde_json::to_value(search_tag)
-                                    .ok()
-                                    .and_then(|a| a.as_str().map(|a| a.to_string()))
-                            })
-                            .collect(),
+                        convert_to_value(
+                            search_tags
+                                .iter()
+                                .filter_map(|search_tag| {
+                                    // TODO: Add trait based inputs instead of converting this to strings
+                                    serde_json::to_value(search_tag)
+                                        .ok()
+                                        .and_then(|a| a.as_str().map(|a| a.to_string()))
+                                })
+                                .collect(),
+                        ),
                     )
                     .switch()?;
             }
@@ -95,7 +107,7 @@ pub async fn msearch_results(
         if let Some(connector) = filters.connector {
             if !connector.is_empty() {
                 query_builder
-                    .add_filter_clause("connector.keyword".to_string(), connector.clone())
+                    .add_filter_clause("connector.keyword".to_string(), convert_to_value(connector))
                     .switch()?;
             }
         };
@@ -104,7 +116,7 @@ pub async fn msearch_results(
                 query_builder
                     .add_filter_clause(
                         "payment_method_type.keyword".to_string(),
-                        payment_method_type.clone(),
+                        convert_to_value(payment_method_type),
                     )
                     .switch()?;
             }
@@ -112,21 +124,47 @@ pub async fn msearch_results(
         if let Some(card_network) = filters.card_network {
             if !card_network.is_empty() {
                 query_builder
-                    .add_filter_clause("card_network.keyword".to_string(), card_network.clone())
+                    .add_filter_clause(
+                        "card_network.keyword".to_string(),
+                        convert_to_value(card_network),
+                    )
                     .switch()?;
             }
         };
         if let Some(card_last_4) = filters.card_last_4 {
             if !card_last_4.is_empty() {
                 query_builder
-                    .add_filter_clause("card_last_4.keyword".to_string(), card_last_4.clone())
+                    .add_filter_clause(
+                        "card_last_4.keyword".to_string(),
+                        convert_to_value(card_last_4),
+                    )
                     .switch()?;
             }
         };
         if let Some(payment_id) = filters.payment_id {
             if !payment_id.is_empty() {
                 query_builder
-                    .add_filter_clause("payment_id.keyword".to_string(), payment_id.clone())
+                    .add_filter_clause(
+                        "payment_id.keyword".to_string(),
+                        convert_to_value(payment_id),
+                    )
+                    .switch()?;
+            }
+        };
+        if let Some(amount) = filters.amount {
+            if !amount.is_empty() {
+                query_builder
+                    .add_filter_clause("amount".to_string(), convert_to_value(amount))
+                    .switch()?;
+            }
+        };
+        if let Some(customer_id) = filters.customer_id {
+            if !customer_id.is_empty() {
+                query_builder
+                    .add_filter_clause(
+                        "customer_id.keyword".to_string(),
+                        convert_to_value(customer_id),
+                    )
                     .switch()?;
             }
         };
@@ -211,21 +249,24 @@ pub async fn search_results(
         if let Some(currency) = filters.currency {
             if !currency.is_empty() {
                 query_builder
-                    .add_filter_clause("currency.keyword".to_string(), currency.clone())
+                    .add_filter_clause("currency.keyword".to_string(), convert_to_value(currency))
                     .switch()?;
             }
         };
         if let Some(status) = filters.status {
             if !status.is_empty() {
                 query_builder
-                    .add_filter_clause("status.keyword".to_string(), status.clone())
+                    .add_filter_clause("status.keyword".to_string(), convert_to_value(status))
                     .switch()?;
             }
         };
         if let Some(payment_method) = filters.payment_method {
             if !payment_method.is_empty() {
                 query_builder
-                    .add_filter_clause("payment_method.keyword".to_string(), payment_method.clone())
+                    .add_filter_clause(
+                        "payment_method.keyword".to_string(),
+                        convert_to_value(payment_method),
+                    )
                     .switch()?;
             }
         };
@@ -234,15 +275,17 @@ pub async fn search_results(
                 query_builder
                     .add_filter_clause(
                         "customer_email.keyword".to_string(),
-                        customer_email
-                            .iter()
-                            .filter_map(|email| {
-                                // TODO: Add trait based inputs instead of converting this to strings
-                                serde_json::to_value(email)
-                                    .ok()
-                                    .and_then(|a| a.as_str().map(|a| a.to_string()))
-                            })
-                            .collect(),
+                        convert_to_value(
+                            customer_email
+                                .iter()
+                                .filter_map(|email| {
+                                    // TODO: Add trait based inputs instead of converting this to strings
+                                    serde_json::to_value(email)
+                                        .ok()
+                                        .and_then(|a| a.as_str().map(|a| a.to_string()))
+                                })
+                                .collect(),
+                        ),
                     )
                     .switch()?;
             }
@@ -252,15 +295,17 @@ pub async fn search_results(
                 query_builder
                     .add_filter_clause(
                         "feature_metadata.search_tags.keyword".to_string(),
-                        search_tags
-                            .iter()
-                            .filter_map(|search_tag| {
-                                // TODO: Add trait based inputs instead of converting this to strings
-                                serde_json::to_value(search_tag)
-                                    .ok()
-                                    .and_then(|a| a.as_str().map(|a| a.to_string()))
-                            })
-                            .collect(),
+                        convert_to_value(
+                            search_tags
+                                .iter()
+                                .filter_map(|search_tag| {
+                                    // TODO: Add trait based inputs instead of converting this to strings
+                                    serde_json::to_value(search_tag)
+                                        .ok()
+                                        .and_then(|a| a.as_str().map(|a| a.to_string()))
+                                })
+                                .collect(),
+                        ),
                     )
                     .switch()?;
             }
@@ -268,7 +313,7 @@ pub async fn search_results(
         if let Some(connector) = filters.connector {
             if !connector.is_empty() {
                 query_builder
-                    .add_filter_clause("connector.keyword".to_string(), connector.clone())
+                    .add_filter_clause("connector.keyword".to_string(), convert_to_value(connector))
                     .switch()?;
             }
         };
@@ -277,7 +322,7 @@ pub async fn search_results(
                 query_builder
                     .add_filter_clause(
                         "payment_method_type.keyword".to_string(),
-                        payment_method_type.clone(),
+                        convert_to_value(payment_method_type),
                     )
                     .switch()?;
             }
@@ -285,21 +330,47 @@ pub async fn search_results(
         if let Some(card_network) = filters.card_network {
             if !card_network.is_empty() {
                 query_builder
-                    .add_filter_clause("card_network.keyword".to_string(), card_network.clone())
+                    .add_filter_clause(
+                        "card_network.keyword".to_string(),
+                        convert_to_value(card_network),
+                    )
                     .switch()?;
             }
         };
         if let Some(card_last_4) = filters.card_last_4 {
             if !card_last_4.is_empty() {
                 query_builder
-                    .add_filter_clause("card_last_4.keyword".to_string(), card_last_4.clone())
+                    .add_filter_clause(
+                        "card_last_4.keyword".to_string(),
+                        convert_to_value(card_last_4),
+                    )
                     .switch()?;
             }
         };
         if let Some(payment_id) = filters.payment_id {
             if !payment_id.is_empty() {
                 query_builder
-                    .add_filter_clause("payment_id.keyword".to_string(), payment_id.clone())
+                    .add_filter_clause(
+                        "payment_id.keyword".to_string(),
+                        convert_to_value(payment_id),
+                    )
+                    .switch()?;
+            }
+        };
+        if let Some(amount) = filters.amount {
+            if !amount.is_empty() {
+                query_builder
+                    .add_filter_clause("amount".to_string(), convert_to_value(amount))
+                    .switch()?;
+            }
+        };
+        if let Some(customer_id) = filters.customer_id {
+            if !customer_id.is_empty() {
+                query_builder
+                    .add_filter_clause(
+                        "customer_id.keyword".to_string(),
+                        convert_to_value(customer_id),
+                    )
                     .switch()?;
             }
         };
diff --git a/crates/api_models/src/analytics/search.rs b/crates/api_models/src/analytics/search.rs
index a33dd100c79..06426efde6c 100644
--- a/crates/api_models/src/analytics/search.rs
+++ b/crates/api_models/src/analytics/search.rs
@@ -14,6 +14,8 @@ pub struct SearchFilters {
     pub card_network: Option<Vec<String>>,
     pub card_last_4: Option<Vec<String>>,
     pub payment_id: Option<Vec<String>>,
+    pub amount: Option<Vec<u64>>,
+    pub customer_id: Option<Vec<String>>,
 }
 impl SearchFilters {
     pub fn is_all_none(&self) -> bool {
@@ -27,6 +29,8 @@ impl SearchFilters {
             && self.card_network.is_none()
             && self.card_last_4.is_none()
             && self.payment_id.is_none()
+            && self.amount.is_none()
+            && self.customer_id.is_none()
     }
 }
 

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

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

## Description
<!-- Describe your changes in detail -->
### `amount` filter
- Added `amount` as a filter / category for global search.
- Payments / Refunds / Disputes can now be searched in a better manner by applying an amount filter in the following - format:
```bash
amount:100
```

- Handling of the field name while building the opensearch query is done.
  - PaymentAttempts / SessionizerPaymentAttempts / PaymentIntents / SessionizerPaymentIntents - amount
  - Refunds / SessionizerRefunds - refund_amount
  - Disputes / SessionizerDisputes - dispute_amount

### `customer_id` filter
- Also added `customer_id` as a filter.
- This will enable searching and filtering customers based on the `customer_id` filter on the `Customers` page, which uses global search.



### 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).
-->
For better search experience based on filters using global search and also on the Customers page.

## 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)?
-->
`amount` filter:

- Hit the curl:

```bash
curl --location 'http://localhost:8080/analytics/v1/search' \
--header 'sec-ch-ua-platform: "macOS"' \
--header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNmNkZTA0NzYtMTFlMi00NGE5LTlkMjUtOTA5M2QzNDQwZjhlIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzM1MDQxMjkzIiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTczNzUzMjA3OSwib3JnX2lkIjoib3JnX2pwanI5TkFEWlhqSENNYTU5MmF3IiwicHJvZmlsZV9pZCI6InByb19QRHUydVA3aWNuM2lXY0I3V0VVSSIsInRlbmFudF9pZCI6InB1YmxpYyJ9.1DAbjUFNOCjpZM_K_NlKj6hOvzP28xLvl01XACE4jPg' \
--header 'Referer: http://localhost:9000/' \
--header 'sec-ch-ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"' \
--header 'sec-ch-ua-mobile: ?0' \
--header 'api-key: dev_ssyiqBXK7Ou1HUQuH0Z80BsHuV5pojc0uWli4QbNXGM1f4pn7jCIyb9VFiHlyATQ' \
--header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36' \
--header 'Content-Type: application/json' \
--data '{
    "query": "usd",
    "filters": {
        "amount": [
            600
        ]
    }
}'
```


- You can see the amount filter getting applied, and should get results after the amount filter is applied.

- Sample opensearch query structure:
  - Sessionizer Payment Intents Index: (Notice the `amount` field in the payload)

```bash
Index: SessionizerPaymentIntents
Payload: {
  "query": {
    "bool": {
      "filter": [
        {
          "multi_match": {
            "type": "phrase",
            "query": "usd",
            "lenient": true
          }
        },
        {
          "terms": {
            "amount": [
              600
            ]
          }
        }
      ],
      "must": [
        {
          "bool": {
            "must": [
              {
                "bool": {
                  "should": [
                    {
                      "bool": {
                        "must": [
                          {
                            "term": {
                              "organization_id.keyword": {
                                "value": "org_jpjr9NADZXjHCMa592aw"
                              }
                            }
                          }
                        ]
                      }
                    }
                  ],
                  "minimum_should_match": 1
                }
              }
            ]
          }
        }
      ]
    }
  },
  "sort": [
    {
      "@timestamp": {
        "order": "desc"
      }
    }
  ]
}
```
  - Sessionizer Refunds Index: (Notice the `refund_amount` being used here)

```bash
Index: SessionizerRefunds
Payload: {
  "query": {
    "bool": {
      "filter": [
        {
          "multi_match": {
            "type": "phrase",
            "query": "usd",
            "lenient": true
          }
        },
        {
          "terms": {
            "refund_amount": [
              600
            ]
          }
        }
      ],
      "must": [
        {
          "bool": {
            "must": [
              {
                "bool": {
                  "should": [
                    {
                      "bool": {
                        "must": [
                          {
                            "term": {
                              "organization_id.keyword": {
                                "value": "org_jpjr9NADZXjHCMa592aw"
                              }
                            }
                          }
                        ]
                      }
                    }
                  ],
                  "minimum_should_match": 1
                }
              }
            ]
          }
        }
      ]
    }
  },
  "sort": [
    {
      "@timestamp": {
        "order": "desc"
      }
    }
  ]
}
```

- Sample response:

```json
[
    {
        "count": 0,
        "index": "payment_attempts",
        "hits": [],
        "status": "Success"
    },
    {
        "count": 0,
        "index": "payment_intents",
        "hits": [],
        "status": "Success"
    },
    {
        "count": 1,
        "index": "refunds",
        "hits": [
            {
                "@timestamp": "2025-01-20T13:02:17Z",
                "attempt_id": "pay_ND5OdiSMSt6lhWovQup1_1",
                "connector": "stripe_test",
                "connector_refund_id": "dummy_ref_isNEfXmc0SW2r0cvaHvc",
                "connector_transaction_id": "pay_dWLQFQ9O5CcNP73haAqF",
                "created_at": 1737378137,
                "currency": "USD",
                "description": "Customer returned product",
                "external_reference_id": "ref_VIVDwLPl0zwwrRSJtnMP",
                "headers": {},
                "internal_reference_id": "refid_EqKX3XCwLUAw9vd8bk9Z",
                "merchant_id": "merchant_1735041293",
                "modified_at": 1737378138,
                "organization_id": "org_jpjr9NADZXjHCMa592aw",
                "payment_id": "pay_ND5OdiSMSt6lhWovQup1",
                "profile_id": "pro_PDu2uP7icn3iWcB7WEUI",
                "refund_amount": 600,
                "refund_arn": "",
                "refund_error_code": null,
                "refund_error_message": null,
                "refund_id": "ref_VIVDwLPl0zwwrRSJtnMP",
                "refund_reason": "Customer returned product",
                "refund_status": "success",
                "refund_type": "instant_refund",
                "sent_to_gateway": true,
                "source_type": "kafka",
                "tenant_id": "public",
                "timestamp": "2025-01-20T13:02:17Z",
                "total_amount": 10000
            }
        ],
        "status": "Success"
    },
    {
        "count": 0,
        "index": "disputes",
        "hits": [],
        "status": "Failure"
    },
    {
        "count": 0,
        "index": "sessionizer_payment_attempts",
        "hits": [],
        "status": "Failure"
    },
    {
        "count": 0,
        "index": "sessionizer_payment_intents",
        "hits": [],
        "status": "Failure"
    },
    {
        "count": 0,
        "index": "sessionizer_refunds",
        "hits": [],
        "status": "Failure"
    },
    {
        "count": 0,
        "index": "sessionizer_disputes",
        "hits": [],
        "status": "Failure"
    }
]
```

`customer_id` filter:
- Hit the curl:

```bash
curl --location 'http://localhost:8080/analytics/v1/search' \
--header 'sec-ch-ua-platform: "macOS"' \
--header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNmNkZTA0NzYtMTFlMi00NGE5LTlkMjUtOTA5M2QzNDQwZjhlIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzM1MDQxMjkzIiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTczODg0MDA2MCwib3JnX2lkIjoib3JnX2pwanI5TkFEWlhqSENNYTU5MmF3IiwicHJvZmlsZV9pZCI6InByb19QRHUydVA3aWNuM2lXY0I3V0VVSSIsInRlbmFudF9pZCI6InB1YmxpYyJ9.frlVM-TVh88J4dxfJgsMoQv5DdLnD6b-qTTr_DIU90s' \
--header 'Referer: http://localhost:9000/' \
--header 'sec-ch-ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"' \
--header 'sec-ch-ua-mobile: ?0' \
--header 'api-key: dev_ssyiqBXK7Ou1HUQuH0Z80BsHuV5pojc0uWli4QbNXGM1f4pn7jCIyb9VFiHlyATQ' \
--header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36' \
--header 'Content-Type: application/json' \
--data '{
    "query": "usd",
    "filters": {
        "customer_id": [
            "StripeCustomer"
        ]
    }
}'
```

- Sample Resposne:

```json
[
    {
        "count": 0,
        "index": "payment_attempts",
        "hits": [],
        "status": "Success"
    },
    {
        "count": 6,
        "index": "payment_intents",
        "hits": [
            {
                "@timestamp": "2025-01-20T13:01:37Z",
                "active_attempt_id": "pay_ND5OdiSMSt6lhWovQup1_1",
                "amount": 10000,
                "amount_captured": 10000,
                "attempt_count": 1,
                "billing_details": null,
                "business_country": null,
                "business_label": "default",
                "client_secret": "pay_ND5OdiSMSt6lhWovQup1_secret_TWKqD8yqhgbyEXowOSe3",
                "connector_id": null,
                "created_at": 1737378097,
                "currency": "USD",
                "customer_email": "f6cd0753a831880835a0e0d23136ef66318d8303605bd37fa80b4ecf248bd303",
                "customer_id": "StripeCustomer",
                "description": "Its my first payment request",
                "feature_metadata": {
                    "apple_pay_recurring_details": null,
                    "redirect_response": null,
                    "search_tags": [
                        "1259195bb05bb44ea78ab60b26a54065183f91b3c5b3c2c074cadcf521305a79",
                        "9bff7b14d0a57a00d98f5eb742418a50b3bdfe5993f16b4219bfa31989bdf80f"
                    ]
                },
                "headers": {},
                "merchant_id": "merchant_1735041293",
                "merchant_order_reference_id": null,
                "metadata": "{\"data2\":\"camel\",\"login_date\":\"2019-09-10T10:11:12Z\",\"new_customer\":\"true\"}",
                "modified_at": 1737378099,
                "off_session": null,
                "organization_id": "org_jpjr9NADZXjHCMa592aw",
                "payment_confirm_source": null,
                "payment_id": "pay_ND5OdiSMSt6lhWovQup1",
                "profile_id": "pro_PDu2uP7icn3iWcB7WEUI",
                "return_url": "https://google.com/",
                "setup_future_usage": null,
                "shipping_details": null,
                "source_type": "kafka",
                "statement_descriptor_name": "joseph",
                "statement_descriptor_suffix": "JS",
                "status": "succeeded",
                "tenant_id": "public",
                "timestamp": "2025-01-20T13:01:37Z"
            },
            {
                "@timestamp": "2025-01-20T09:56:07Z",
                "active_attempt_id": "pay_bWGMDT130V4mrDskx9Lt_1",
                "amount": 6540,
                "amount_captured": 6540,
                "attempt_count": 1,
                "billing_details": null,
                "business_country": null,
                "business_label": "default",
                "client_secret": "pay_bWGMDT130V4mrDskx9Lt_secret_VnTfCd82o07H59gQOmdA",
                "connector_id": null,
                "created_at": 1737366967,
                "currency": "USD",
                "customer_email": "6ede5d9641558aef42ea1cff12554f6d6f56081319465af002f0842aec34c763",
                "customer_id": "StripeCustomer",
                "description": "Its my first payment request",
                "feature_metadata": null,
                "headers": {},
                "merchant_id": "merchant_1735041293",
                "merchant_order_reference_id": null,
                "metadata": "{\"udf1\":\"value1\",\"login_date\":\"2019-09-10T10:11:12Z\",\"new_customer\":\"true\"}",
                "modified_at": 1737366971,
                "off_session": null,
                "organization_id": "org_jpjr9NADZXjHCMa592aw",
                "payment_confirm_source": null,
                "payment_id": "pay_bWGMDT130V4mrDskx9Lt",
                "profile_id": "pro_PDu2uP7icn3iWcB7WEUI",
                "return_url": "https://google.com/",
                "setup_future_usage": null,
                "shipping_details": null,
                "source_type": "kafka",
                "statement_descriptor_name": "joseph",
                "statement_descriptor_suffix": "JS",
                "status": "succeeded",
                "tenant_id": "public",
                "timestamp": "2025-01-20T09:56:07Z"
            },
            {
                "@timestamp": "2025-01-20T09:50:20Z",
                "active_attempt_id": "pay_OEYLy5ZKarSVBVOyhsON_1",
                "amount": 6540,
                "amount_captured": 6540,
                "attempt_count": 1,
                "billing_details": null,
                "business_country": null,
                "business_label": "default",
                "client_secret": "pay_OEYLy5ZKarSVBVOyhsON_secret_EmNPjhoI9FK8aVO30Ss4",
                "connector_id": null,
                "created_at": 1737366620,
                "currency": "USD",
                "customer_email": "6ede5d9641558aef42ea1cff12554f6d6f56081319465af002f0842aec34c763",
                "customer_id": "StripeCustomer",
                "description": "Its my first payment request",
                "feature_metadata": null,
                "headers": {},
                "merchant_id": "merchant_1735041293",
                "merchant_order_reference_id": null,
                "metadata": "{\"udf1\":\"value1\",\"login_date\":\"2019-09-10T10:11:12Z\",\"new_customer\":\"true\"}",
                "modified_at": 1737366627,
                "off_session": null,
                "organization_id": "org_jpjr9NADZXjHCMa592aw",
                "payment_confirm_source": null,
                "payment_id": "pay_OEYLy5ZKarSVBVOyhsON",
                "profile_id": "pro_PDu2uP7icn3iWcB7WEUI",
                "return_url": "https://google.com/",
                "setup_future_usage": null,
                "shipping_details": null,
                "source_type": "kafka",
                "statement_descriptor_name": "joseph",
                "statement_descriptor_suffix": "JS",
                "status": "succeeded",
                "tenant_id": "public",
                "timestamp": "2025-01-20T09:50:20Z"
            },
            {
                "@timestamp": "2025-01-20T09:41:41Z",
                "active_attempt_id": "pay_lJJCj6jGDPPOtwb3YucZ_1",
                "amount": 10000,
                "amount_captured": 10000,
                "attempt_count": 1,
                "billing_details": null,
                "business_country": null,
                "business_label": "default",
                "client_secret": "pay_lJJCj6jGDPPOtwb3YucZ_secret_xDqPGN0uREz1mSDLeQv9",
                "connector_id": null,
                "created_at": 1737366101,
                "currency": "USD",
                "customer_email": "f6cd0753a831880835a0e0d23136ef66318d8303605bd37fa80b4ecf248bd303",
                "customer_id": "StripeCustomer",
                "description": "Its my first payment request",
                "feature_metadata": {
                    "apple_pay_recurring_details": null,
                    "redirect_response": null,
                    "search_tags": [
                        "1259195bb05bb44ea78ab60b26a54065183f91b3c5b3c2c074cadcf521305a79",
                        "9bff7b14d0a57a00d98f5eb742418a50b3bdfe5993f16b4219bfa31989bdf80f"
                    ]
                },
                "headers": {},
                "merchant_id": "merchant_1735041293",
                "merchant_order_reference_id": null,
                "metadata": "{\"data2\":\"camel\",\"login_date\":\"2019-09-10T10:11:12Z\",\"new_customer\":\"true\"}",
                "modified_at": 1737366102,
                "off_session": null,
                "organization_id": "org_jpjr9NADZXjHCMa592aw",
                "payment_confirm_source": null,
                "payment_id": "pay_lJJCj6jGDPPOtwb3YucZ",
                "profile_id": "pro_PDu2uP7icn3iWcB7WEUI",
                "return_url": "https://google.com/",
                "setup_future_usage": null,
                "shipping_details": null,
                "source_type": "kafka",
                "statement_descriptor_name": "joseph",
                "statement_descriptor_suffix": "JS",
                "status": "succeeded",
                "tenant_id": "public",
                "timestamp": "2025-01-20T09:41:41Z"
            },
            {
                "@timestamp": "2025-01-20T08:13:08Z",
                "active_attempt_id": "pay_A3dPrR94eHFTlvT7CSXT_1",
                "amount": 10000,
                "amount_captured": 10000,
                "attempt_count": 1,
                "billing_details": null,
                "business_country": null,
                "business_label": "default",
                "client_secret": "pay_A3dPrR94eHFTlvT7CSXT_secret_3FUMXfThPXlRp0kZyvPc",
                "connector_id": null,
                "created_at": 1737360788,
                "currency": "USD",
                "customer_email": "f6cd0753a831880835a0e0d23136ef66318d8303605bd37fa80b4ecf248bd303",
                "customer_id": "StripeCustomer",
                "description": "Its my first payment request",
                "feature_metadata": {
                    "apple_pay_recurring_details": null,
                    "redirect_response": null,
                    "search_tags": [
                        "1259195bb05bb44ea78ab60b26a54065183f91b3c5b3c2c074cadcf521305a79",
                        "9bff7b14d0a57a00d98f5eb742418a50b3bdfe5993f16b4219bfa31989bdf80f"
                    ]
                },
                "headers": {},
                "merchant_id": "merchant_1735041293",
                "merchant_order_reference_id": null,
                "metadata": "{\"data2\":\"camel\",\"login_date\":\"2019-09-10T10:11:12Z\",\"new_customer\":\"true\"}",
                "modified_at": 1737360790,
                "off_session": null,
                "organization_id": "org_jpjr9NADZXjHCMa592aw",
                "payment_confirm_source": null,
                "payment_id": "pay_A3dPrR94eHFTlvT7CSXT",
                "profile_id": "pro_PDu2uP7icn3iWcB7WEUI",
                "return_url": "https://google.com/",
                "setup_future_usage": null,
                "shipping_details": null,
                "source_type": "kafka",
                "statement_descriptor_name": "joseph",
                "statement_descriptor_suffix": "JS",
                "status": "succeeded",
                "tenant_id": "public",
                "timestamp": "2025-01-20T08:13:08Z"
            },
            {
                "@timestamp": "2024-12-24T11:56:12Z",
                "active_attempt_id": "pay_5cvsiaRXZRzRg6VNvJ5e_1",
                "amount": 10000,
                "amount_captured": 10000,
                "attempt_count": 1,
                "billing_details": null,
                "business_country": null,
                "business_label": "default",
                "client_secret": "pay_5cvsiaRXZRzRg6VNvJ5e_secret_XRpkHJHWahWqIaYKiovr",
                "connector_id": null,
                "created_at": 1735041372,
                "currency": "USD",
                "customer_email": "f6cd0753a831880835a0e0d23136ef66318d8303605bd37fa80b4ecf248bd303",
                "customer_id": "StripeCustomer",
                "description": "Its my first payment request",
                "feature_metadata": {
                    "redirect_response": null,
                    "search_tags": [
                        "1259195bb05bb44ea78ab60b26a54065183f91b3c5b3c2c074cadcf521305a79",
                        "9bff7b14d0a57a00d98f5eb742418a50b3bdfe5993f16b4219bfa31989bdf80f"
                    ]
                },
                "headers": {},
                "merchant_id": "merchant_1735041293",
                "merchant_order_reference_id": null,
                "metadata": "{\"data2\":\"camel\",\"login_date\":\"2019-09-10T10:11:12Z\",\"new_customer\":\"true\"}",
                "modified_at": 1735041373,
                "off_session": null,
                "organization_id": "org_jpjr9NADZXjHCMa592aw",
                "payment_confirm_source": null,
                "payment_id": "pay_5cvsiaRXZRzRg6VNvJ5e",
                "profile_id": "pro_PDu2uP7icn3iWcB7WEUI",
                "return_url": "https://google.com/",
                "setup_future_usage": null,
                "shipping_details": null,
                "source_type": "kafka",
                "statement_descriptor_name": "joseph",
                "statement_descriptor_suffix": "JS",
                "status": "succeeded",
                "tenant_id": "public",
                "timestamp": "2024-12-24T11:56:12Z"
            }
        ],
        "status": "Success"
    },
    {
        "count": 0,
        "index": "refunds",
        "hits": [],
        "status": "Success"
    },
    {
        "count": 0,
        "index": "disputes",
        "hits": [],
        "status": "Failure"
    },
    {
        "count": 0,
        "index": "sessionizer_payment_attempts",
        "hits": [],
        "status": "Failure"
    },
    {
        "count": 0,
        "index": "sessionizer_payment_intents",
        "hits": [],
        "status": "Failure"
    },
    {
        "count": 0,
        "index": "sessionizer_refunds",
        "hits": [],
        "status": "Failure"
    },
    {
        "count": 0,
        "index": "sessionizer_disputes",
        "hits": [],
        "status": "Failure"
    }
]
```

- `customer_id` filter getting applied when building the opensearch query:

```bash
Index: PaymentIntents
Payload: {
  "query": {
    "bool": {
      "filter": [
        {
          "multi_match": {
            "type": "phrase",
            "query": "usd",
            "lenient": true
          }
        },
        {
          "terms": {
            "customer_id.keyword": [
              "StripeCustomer"
            ]
          }
        }
      ],
      "must": [
        {
          "bool": {
            "must": [
              {
                "bool": {
                  "should": [
                    {
                      "bool": {
                        "must": [
                          {
                            "term": {
                              "organization_id.keyword": {
                                "value": "org_jpjr9NADZXjHCMa592aw"
                              }
                            }
                          }
                        ]
                      }
                    }
                  ],
                  "minimum_should_match": 1
                }
              }
            ]
          }
        }
      ]
    }
  },
  "sort": [
    {
      "@timestamp": {
        "order": "desc"
      }
    }
  ]
}
```
## 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
- [ ] I added unit tests for my changes where possible
