Bug: [FEATURE] return all the `missing_fields` in a request



### Feature Description

During address validation, It would be a good experience to have all the missing fields returned at once instead of making many requests to know the missing fields.

Referring the discussion here https://github.com/juspay/hyperswitch/pull/441#issuecomment-1399482111

### Possible Implementation

A separate error type could be implemented which will take a vector or missing fields and return it.

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

- [X] I checked and didn't find 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?

Yes I am willing to submit a PR!
diff --git a/.gitignore b/.gitignore
index ced4be3d728..a8e6412fb1a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -256,3 +256,5 @@ loadtest/*.tmp/
 
 # Nix output
 result*
+
+.idea/
diff --git a/Cargo.lock b/Cargo.lock
index 99a37d53a60..f9c1f539918 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -107,6 +107,44 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "actix-multipart"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dee489e3c01eae4d1c35b03c4493f71cb40d93f66b14558feb1b1a807671cc4e"
+dependencies = [
+ "actix-multipart-derive",
+ "actix-utils",
+ "actix-web",
+ "bytes",
+ "derive_more",
+ "futures-core",
+ "futures-util",
+ "httparse",
+ "local-waker",
+ "log",
+ "memchr",
+ "mime",
+ "serde",
+ "serde_json",
+ "serde_plain",
+ "tempfile",
+ "tokio",
+]
+
+[[package]]
+name = "actix-multipart-derive"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ec592f234db8a253cf80531246a4407c8a70530423eea80688a6c5a44a110e7"
+dependencies = [
+ "darling",
+ "parse-size",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "actix-router"
 version = "0.5.1"
@@ -629,6 +667,39 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "aws-sdk-s3"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "392b9811ca489747ac84349790e49deaa1f16631949e7dd4156000251c260eae"
+dependencies = [
+ "aws-credential-types",
+ "aws-endpoint",
+ "aws-http",
+ "aws-sig-auth",
+ "aws-sigv4",
+ "aws-smithy-async",
+ "aws-smithy-checksums",
+ "aws-smithy-client",
+ "aws-smithy-eventstream",
+ "aws-smithy-http",
+ "aws-smithy-http-tower",
+ "aws-smithy-json",
+ "aws-smithy-types",
+ "aws-smithy-xml",
+ "aws-types",
+ "bytes",
+ "http",
+ "http-body",
+ "once_cell",
+ "percent-encoding",
+ "regex",
+ "tokio-stream",
+ "tower",
+ "tracing",
+ "url",
+]
+
 [[package]]
 name = "aws-sdk-sso"
 version = "0.26.0"
@@ -688,6 +759,7 @@ checksum = "24d77d879ab210e958ba65a6d3842969a596738c024989cd3e490cf9f9b560ec"
 dependencies = [
  "aws-credential-types",
  "aws-sigv4",
+ "aws-smithy-eventstream",
  "aws-smithy-http",
  "aws-types",
  "http",
@@ -700,7 +772,9 @@ version = "0.55.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4ab4eebc8ec484fb9eab04b15a5d1e71f3dc13bee8fdd2d9ed78bcd6ecbd7192"
 dependencies = [
+ "aws-smithy-eventstream",
  "aws-smithy-http",
+ "bytes",
  "form_urlencoded",
  "hex",
  "hmac",
@@ -725,6 +799,27 @@ dependencies = [
  "tokio-stream",
 ]
 
+[[package]]
+name = "aws-smithy-checksums"
+version = "0.55.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71a63d4f1c04b3abb7603001e4513f19617427bf27ca185b2ac663a1e342d39e"
+dependencies = [
+ "aws-smithy-http",
+ "aws-smithy-types",
+ "bytes",
+ "crc32c",
+ "crc32fast",
+ "hex",
+ "http",
+ "http-body",
+ "md-5",
+ "pin-project-lite",
+ "sha1",
+ "sha2",
+ "tracing",
+]
+
 [[package]]
 name = "aws-smithy-client"
 version = "0.55.1"
@@ -749,12 +844,24 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "aws-smithy-eventstream"
+version = "0.55.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "168f08f8439c8b317b578a695e514c5cd7b869e73849a2d6b71ced4de6ce193d"
+dependencies = [
+ "aws-smithy-types",
+ "bytes",
+ "crc32fast",
+]
+
 [[package]]
 name = "aws-smithy-http"
 version = "0.55.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "03bcc02d7ed9649d855c8ce4a735e9848d7b8f7568aad0504c158e3baa955df8"
 dependencies = [
+ "aws-smithy-eventstream",
  "aws-smithy-types",
  "bytes",
  "bytes-utils",
@@ -1086,6 +1193,17 @@ dependencies = [
  "jobserver",
 ]
 
+[[package]]
+name = "cfb"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
+dependencies = [
+ "byteorder",
+ "fnv",
+ "uuid",
+]
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -1103,7 +1221,7 @@ dependencies = [
  "num-integer",
  "num-traits",
  "serde",
- "time 0.1.43",
+ "time 0.1.45",
  "wasm-bindgen",
  "winapi",
 ]
@@ -1286,6 +1404,15 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff"
 
+[[package]]
+name = "crc32c"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfea2db42e9927a3845fb268a10a72faed6d416065f77873f05e411457c363e"
+dependencies = [
+ "rustc_version",
+]
+
 [[package]]
 name = "crc32fast"
 version = "1.3.2"
@@ -2116,7 +2243,7 @@ dependencies = [
  "base64 0.13.1",
  "futures-lite",
  "http",
- "infer",
+ "infer 0.2.3",
  "pin-project-lite",
  "rand 0.7.3",
  "serde",
@@ -2268,6 +2395,15 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
 
+[[package]]
+name = "infer"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc"
+dependencies = [
+ "cfb",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -2552,6 +2688,15 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "md-5"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca"
+dependencies = [
+ "digest",
+]
+
 [[package]]
 name = "md5"
 version = "0.7.0"
@@ -2914,6 +3059,12 @@ dependencies = [
  "windows-sys 0.45.0",
 ]
 
+[[package]]
+name = "parse-size"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae"
+
 [[package]]
 name = "paste"
 version = "1.0.12"
@@ -3400,6 +3551,7 @@ dependencies = [
  "js-sys",
  "log",
  "mime",
+ "mime_guess",
  "native-tls",
  "once_cell",
  "percent-encoding",
@@ -3457,12 +3609,15 @@ dependencies = [
  "actix",
  "actix-cors",
  "actix-http",
+ "actix-multipart",
  "actix-rt",
  "actix-web",
  "api_models",
  "async-bb8-diesel",
  "async-trait",
  "awc",
+ "aws-config",
+ "aws-sdk-s3",
  "base64 0.21.0",
  "bb8",
  "blake3",
@@ -3482,6 +3637,7 @@ dependencies = [
  "futures",
  "hex",
  "http",
+ "infer 0.13.0",
  "josekit",
  "jsonwebtoken",
  "literally",
@@ -3816,6 +3972,15 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_plain"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "serde_qs"
 version = "0.8.5"
@@ -4255,11 +4420,12 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.1.43"
+version = "0.1.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
 dependencies = [
  "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
  "winapi",
 ]
 
@@ -4853,6 +5019,12 @@ version = "0.9.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
 
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
 [[package]]
 name = "wasi"
 version = "0.11.0+wasi-snapshot-preview1"
diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml
index bd3d6c6dca3..a8f24cb6603 100644
--- a/crates/common_utils/Cargo.toml
+++ b/crates/common_utils/Cargo.toml
@@ -37,7 +37,6 @@ ring = "0.16.20"
 serde = { version = "1.0.160", features = ["derive"] }
 serde_json = "1.0.96"
 serde_urlencoded = "0.7.1"
-signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"], optional = true }
 signal-hook = { version = "0.3.15", optional = true }
 tokio = { version = "1.27.0", features = ["macros", "rt-multi-thread"], optional = true }
 thiserror = "1.0.40"
@@ -48,6 +47,9 @@ md5 = "0.7.0"
 masking = { version = "0.1.0", path = "../masking" }
 router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"], optional = true }
 
+[target.'cfg(not(target_os = "windows"))'.dependencies]
+signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"], optional = true }
+
 [dev-dependencies]
 fake = "2.5.0"
 proptest = "1.1.0"
diff --git a/crates/common_utils/src/signals.rs b/crates/common_utils/src/signals.rs
index 44118f39e30..5bde366bf3c 100644
--- a/crates/common_utils/src/signals.rs
+++ b/crates/common_utils/src/signals.rs
@@ -1,6 +1,8 @@
 //! Provide Interface for worker services to handle signals
 
+#[cfg(not(target_os = "windows"))]
 use futures::StreamExt;
+#[cfg(not(target_os = "windows"))]
 use router_env::logger;
 use tokio::sync::mpsc;
 
@@ -8,6 +10,7 @@ use tokio::sync::mpsc;
 /// This functions is meant to run in parallel to the application.
 /// It will send a signal to the receiver when a SIGTERM or SIGINT is received
 ///
+#[cfg(not(target_os = "windows"))]
 pub async fn signal_handler(mut sig: signal_hook_tokio::Signals, sender: mpsc::Sender<()>) {
     if let Some(signal) = sig.next().await {
         logger::info!(
@@ -31,9 +34,47 @@ pub async fn signal_handler(mut sig: signal_hook_tokio::Signals, sender: mpsc::S
     }
 }
 
+///
+/// This functions is meant to run in parallel to the application.
+/// It will send a signal to the receiver when a SIGTERM or SIGINT is received
+///
+#[cfg(target_os = "windows")]
+pub async fn signal_handler(_sig: DummySignal, _sender: mpsc::Sender<()>) {}
+
 ///
 /// This function is used to generate a list of signals that the signal_handler should listen for
 ///
+#[cfg(not(target_os = "windows"))]
 pub fn get_allowed_signals() -> Result<signal_hook_tokio::SignalsInfo, std::io::Error> {
     signal_hook_tokio::Signals::new([signal_hook::consts::SIGTERM, signal_hook::consts::SIGINT])
 }
+
+///
+/// This function is used to generate a list of signals that the signal_handler should listen for
+///
+#[cfg(target_os = "windows")]
+pub fn get_allowed_signals() -> Result<DummySignal, std::io::Error> {
+    Ok(DummySignal)
+}
+
+///
+/// Dummy Signal Handler for windows
+///
+#[cfg(target_os = "windows")]
+#[derive(Debug, Clone)]
+pub struct DummySignal;
+
+#[cfg(target_os = "windows")]
+impl DummySignal {
+    ///
+    /// Dummy handler for signals in windows (empty)
+    ///
+    pub fn handle(&self) -> Self {
+        self.clone()
+    }
+
+    ///
+    /// Hollow implementation, for windows compatibility
+    ///
+    pub fn close(self) {}
+}
diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml
index be6d7cf5bba..102d0287ff7 100644
--- a/crates/router/Cargo.toml
+++ b/crates/router/Cargo.toml
@@ -69,7 +69,6 @@ serde_path_to_error = "0.1.11"
 serde_qs = { version = "0.12.0", optional = true }
 serde_urlencoded = "0.7.1"
 serde_with = "2.3.2"
-signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"] }
 signal-hook = "0.3.15"
 strum = { version = "0.24.1", features = ["derive"] }
 thiserror = "1.0.40"
@@ -94,6 +93,9 @@ aws-sdk-s3 = "0.25.0"
 aws-config = "0.55.1"
 infer = "0.13.0"
 
+[target.'cfg(not(target_os = "windows"))'.dependencies]
+signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"]}
+
 [build-dependencies]
 router_env = { version = "0.1.0", path = "../router_env", default-features = false }
 
diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs
index 7b7cbe86945..6c355d52eef 100644
--- a/crates/router/src/connector/stripe/transformers.rs
+++ b/crates/router/src/connector/stripe/transformers.rs
@@ -1,6 +1,6 @@
 use api_models::{self, enums as api_enums, payments};
 use base64::Engine;
-use common_utils::{errors::CustomResult, fp_utils, pii};
+use common_utils::{errors::CustomResult, pii};
 use error_stack::{IntoReport, ResultExt};
 use masking::{ExposeInterface, ExposeOptionInterface, Secret};
 use serde::{Deserialize, Serialize};
@@ -8,7 +8,7 @@ use url::Url;
 use uuid::Uuid;
 
 use crate::{
-    consts,
+    collect_missing_value_keys, consts,
     core::errors,
     services,
     types::{self, api, storage::enums},
@@ -429,30 +429,21 @@ fn validate_shipping_address_against_payment_method(
     payment_method: &StripePaymentMethodType,
 ) -> Result<(), error_stack::Report<errors::ConnectorError>> {
     if let StripePaymentMethodType::AfterpayClearpay = payment_method {
-        fp_utils::when(shipping_address.name.is_none(), || {
-            Err(errors::ConnectorError::MissingRequiredField {
-                field_name: "shipping.address.first_name",
-            })
-        })?;
-
-        fp_utils::when(shipping_address.line1.is_none(), || {
-            Err(errors::ConnectorError::MissingRequiredField {
-                field_name: "shipping.address.line1",
-            })
-        })?;
-
-        fp_utils::when(shipping_address.country.is_none(), || {
-            Err(errors::ConnectorError::MissingRequiredField {
-                field_name: "shipping.address.country",
-            })
-        })?;
+        let missing_fields = collect_missing_value_keys!(
+            ("shipping.address.first_name", shipping_address.name),
+            ("shipping.address.line1", shipping_address.line1),
+            ("shipping.address.country", shipping_address.country),
+            ("shipping.address.zip", shipping_address.zip)
+        );
 
-        fp_utils::when(shipping_address.zip.is_none(), || {
-            Err(errors::ConnectorError::MissingRequiredField {
-                field_name: "shipping.address.zip",
+        if !missing_fields.is_empty() {
+            return Err(errors::ConnectorError::MissingRequiredFields {
+                field_names: missing_fields,
             })
-        })?;
+            .into_report();
+        }
     }
+
     Ok(())
 }
 
@@ -1799,3 +1790,192 @@ pub struct DisputeObj {
     pub dispute_id: String,
     pub status: String,
 }
+
+#[cfg(test)]
+mod test_validate_shipping_address_against_payment_method {
+    #![allow(clippy::unwrap_used)]
+    use api_models::enums::CountryCode;
+    use masking::Secret;
+
+    use crate::{
+        connector::stripe::transformers::{
+            validate_shipping_address_against_payment_method, StripePaymentMethodType,
+            StripeShippingAddress,
+        },
+        core::errors,
+    };
+
+    #[test]
+    fn should_return_ok() {
+        // Arrange
+        let stripe_shipping_address = create_stripe_shipping_address(
+            Some("name".to_string()),
+            Some("line1".to_string()),
+            Some(CountryCode::AD),
+            Some("zip".to_string()),
+        );
+
+        let payment_method = &StripePaymentMethodType::AfterpayClearpay;
+
+        //Act
+        let result = validate_shipping_address_against_payment_method(
+            &stripe_shipping_address,
+            payment_method,
+        );
+
+        // Assert
+        assert!(result.is_ok());
+    }
+
+    #[test]
+    fn should_return_err_for_empty_name() {
+        // Arrange
+        let stripe_shipping_address = create_stripe_shipping_address(
+            None,
+            Some("line1".to_string()),
+            Some(CountryCode::AD),
+            Some("zip".to_string()),
+        );
+
+        let payment_method = &StripePaymentMethodType::AfterpayClearpay;
+
+        //Act
+        let result = validate_shipping_address_against_payment_method(
+            &stripe_shipping_address,
+            payment_method,
+        );
+
+        // Assert
+        assert!(result.is_err());
+        let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned();
+        assert_eq!(missing_fields.len(), 1);
+        assert_eq!(missing_fields[0], "shipping.address.first_name");
+    }
+
+    #[test]
+    fn should_return_err_for_empty_line1() {
+        // Arrange
+        let stripe_shipping_address = create_stripe_shipping_address(
+            Some("name".to_string()),
+            None,
+            Some(CountryCode::AD),
+            Some("zip".to_string()),
+        );
+
+        let payment_method = &StripePaymentMethodType::AfterpayClearpay;
+
+        //Act
+        let result = validate_shipping_address_against_payment_method(
+            &stripe_shipping_address,
+            payment_method,
+        );
+
+        // Assert
+        assert!(result.is_err());
+        let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned();
+        assert_eq!(missing_fields.len(), 1);
+        assert_eq!(missing_fields[0], "shipping.address.line1");
+    }
+
+    #[test]
+    fn should_return_err_for_empty_country() {
+        // Arrange
+        let stripe_shipping_address = create_stripe_shipping_address(
+            Some("name".to_string()),
+            Some("line1".to_string()),
+            None,
+            Some("zip".to_string()),
+        );
+
+        let payment_method = &StripePaymentMethodType::AfterpayClearpay;
+
+        //Act
+        let result = validate_shipping_address_against_payment_method(
+            &stripe_shipping_address,
+            payment_method,
+        );
+
+        // Assert
+        assert!(result.is_err());
+        let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned();
+        assert_eq!(missing_fields.len(), 1);
+        assert_eq!(missing_fields[0], "shipping.address.country");
+    }
+
+    #[test]
+    fn should_return_err_for_empty_zip() {
+        // Arrange
+        let stripe_shipping_address = create_stripe_shipping_address(
+            Some("name".to_string()),
+            Some("line1".to_string()),
+            Some(CountryCode::AD),
+            None,
+        );
+        let payment_method = &StripePaymentMethodType::AfterpayClearpay;
+
+        //Act
+        let result = validate_shipping_address_against_payment_method(
+            &stripe_shipping_address,
+            payment_method,
+        );
+
+        // Assert
+        assert!(result.is_err());
+        let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned();
+        assert_eq!(missing_fields.len(), 1);
+        assert_eq!(missing_fields[0], "shipping.address.zip");
+    }
+
+    #[test]
+    fn should_return_error_when_missing_multiple_fields() {
+        // Arrange
+        let expected_missing_field_names: Vec<&'static str> =
+            vec!["shipping.address.zip", "shipping.address.country"];
+        let stripe_shipping_address = create_stripe_shipping_address(
+            Some("name".to_string()),
+            Some("line1".to_string()),
+            None,
+            None,
+        );
+        let payment_method = &StripePaymentMethodType::AfterpayClearpay;
+
+        //Act
+        let result = validate_shipping_address_against_payment_method(
+            &stripe_shipping_address,
+            payment_method,
+        );
+
+        // Assert
+        assert!(result.is_err());
+        let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned();
+        for field in missing_fields {
+            assert!(expected_missing_field_names.contains(&field));
+        }
+    }
+
+    fn get_missing_fields(connector_error: &errors::ConnectorError) -> Vec<&'static str> {
+        if let errors::ConnectorError::MissingRequiredFields { field_names } = connector_error {
+            return field_names.to_vec();
+        }
+
+        vec![]
+    }
+
+    fn create_stripe_shipping_address(
+        name: Option<String>,
+        line1: Option<String>,
+        country: Option<CountryCode>,
+        zip: Option<String>,
+    ) -> StripeShippingAddress {
+        StripeShippingAddress {
+            name: name.map(Secret::new),
+            line1: line1.map(Secret::new),
+            country,
+            zip: zip.map(Secret::new),
+            city: Some(String::from("city")),
+            line2: Some(Secret::new(String::from("line2"))),
+            state: Some(Secret::new(String::from("state"))),
+            phone: Some(Secret::new(String::from("pbone number"))),
+        }
+    }
+}
diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs
index c4a6a7d863e..1377f3019a7 100644
--- a/crates/router/src/core/errors.rs
+++ b/crates/router/src/core/errors.rs
@@ -241,6 +241,8 @@ pub enum ConnectorError {
     ResponseHandlingFailed,
     #[error("Missing required field: {field_name}")]
     MissingRequiredField { field_name: &'static str },
+    #[error("Missing required fields: {field_names:?}")]
+    MissingRequiredFields { field_names: Vec<&'static str> },
     #[error("Failed to obtain authentication type")]
     FailedToObtainAuthType,
     #[error("Failed to obtain certificate")]
diff --git a/crates/router/src/macros.rs b/crates/router/src/macros.rs
index 2cd6310faf0..33ed43fcc7a 100644
--- a/crates/router/src/macros.rs
+++ b/crates/router/src/macros.rs
@@ -51,3 +51,18 @@ macro_rules! async_spawn {
         tokio::spawn(async move { $t });
     };
 }
+
+#[macro_export]
+macro_rules! collect_missing_value_keys {
+    [$(($key:literal, $option:expr)),+] => {
+        {
+            let mut keys: Vec<&'static str> = Vec::new();
+            $(
+                if $option.is_none() {
+                    keys.push($key);
+                }
+            )*
+            keys
+        }
+    };
+}
diff --git a/crates/router/src/scheduler/utils.rs b/crates/router/src/scheduler/utils.rs
index 24ddf6b2a86..a58b02561a8 100644
--- a/crates/router/src/scheduler/utils.rs
+++ b/crates/router/src/scheduler/utils.rs
@@ -4,8 +4,11 @@ use std::{
 };
 
 use error_stack::{report, ResultExt};
+#[cfg(not(target_os = "windows"))]
+use futures::StreamExt;
 use redis_interface::{RedisConnectionPool, RedisEntryId};
 use router_env::opentelemetry;
+use tokio::sync::oneshot;
 use uuid::Uuid;
 
 use super::{consumer, metrics, process_data, workflows};
@@ -376,3 +379,36 @@ where
         Ok(())
     }
 }
+
+#[cfg(not(target_os = "windows"))]
+pub(crate) async fn signal_handler(
+    mut sig: signal_hook_tokio::Signals,
+    sender: oneshot::Sender<()>,
+) {
+    if let Some(signal) = sig.next().await {
+        logger::info!(
+            "Received signal: {:?}",
+            signal_hook::low_level::signal_name(signal)
+        );
+        match signal {
+            signal_hook::consts::SIGTERM | signal_hook::consts::SIGINT => match sender.send(()) {
+                Ok(_) => {
+                    logger::info!("Request for force shutdown received")
+                }
+                Err(_) => {
+                    logger::error!(
+                        "The receiver is closed, a termination call might already be sent"
+                    )
+                }
+            },
+            _ => {}
+        }
+    }
+}
+
+#[cfg(target_os = "windows")]
+pub(crate) async fn signal_handler(
+    _sig: common_utils::signals::DummySignal,
+    _sender: oneshot::Sender<()>,
+) {
+}

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

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates

## Description
<!-- Describe your changes in detail -->
On address field validation return all the mandatory fields having a null or empty values.

### Additional Changes

- [ ] 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
<!--
During address validation, It would be a good experience to have all the missing fields returned at once instead of making many requests to know the missing fields.

As mentioned in https://github.com/juspay/hyperswitch/issues/451
-->
Closes #451.

## 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)?

Unit tests are provided for the changes
-->


## 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`
- [ ] I reviewed submitted code
- [x] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable