Bug: [FEATURE] add tests for stripe connector



### Feature Description

As part of adding unit tests to existing connectors we are adding test for stripe

### Possible Implementation

add a test file for stripe connector in the crates/router/tests folder

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

None
diff --git a/crates/router/tests/connectors/connector_auth.rs b/crates/router/tests/connectors/connector_auth.rs
index cd696c9e0b4..01505e6a01e 100644
--- a/crates/router/tests/connectors/connector_auth.rs
+++ b/crates/router/tests/connectors/connector_auth.rs
@@ -12,6 +12,7 @@ pub(crate) struct ConnectorAuthentication {
     pub payu: Option<BodyKey>,
     pub rapyd: Option<BodyKey>,
     pub shift4: Option<HeaderKey>,
+    pub stripe: Option<HeaderKey>,
     pub worldpay: Option<HeaderKey>,
     pub worldline: Option<SignatureKey>,
 }
diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs
index cca173d6020..87e15657a2c 100644
--- a/crates/router/tests/connectors/main.rs
+++ b/crates/router/tests/connectors/main.rs
@@ -10,6 +10,7 @@ mod globalpay;
 mod payu;
 mod rapyd;
 mod shift4;
+mod stripe;
 mod utils;
 mod worldline;
 mod worldpay;
diff --git a/crates/router/tests/connectors/stripe.rs b/crates/router/tests/connectors/stripe.rs
new file mode 100644
index 00000000000..612086bc0ea
--- /dev/null
+++ b/crates/router/tests/connectors/stripe.rs
@@ -0,0 +1,337 @@
+use std::time::Duration;
+
+use masking::Secret;
+use router::types::{self, api, storage::enums};
+
+use crate::{
+    connector_auth,
+    utils::{self, ConnectorActions},
+};
+
+struct Stripe;
+impl ConnectorActions for Stripe {}
+impl utils::Connector for Stripe {
+    fn get_data(&self) -> types::api::ConnectorData {
+        use router::connector::Stripe;
+        types::api::ConnectorData {
+            connector: Box::new(&Stripe),
+            connector_name: types::Connector::Stripe,
+            get_token: types::api::GetToken::Connector,
+        }
+    }
+
+    fn get_auth_token(&self) -> types::ConnectorAuthType {
+        types::ConnectorAuthType::from(
+            connector_auth::ConnectorAuthentication::new()
+                .stripe
+                .expect("Missing connector authentication configuration"),
+        )
+    }
+
+    fn get_name(&self) -> String {
+        "stripe".to_string()
+    }
+}
+
+fn get_payment_authorize_data() -> Option<types::PaymentsAuthorizeData> {
+    Some(types::PaymentsAuthorizeData {
+        payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+            card_number: Secret::new("4242424242424242".to_string()),
+            ..utils::CCardType::default().0
+        }),
+        ..utils::PaymentAuthorizeType::default().0
+    })
+}
+
+#[actix_web::test]
+async fn should_only_authorize_payment() {
+    let response = Stripe {}
+        .authorize_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::Authorized);
+}
+
+#[actix_web::test]
+async fn should_authorize_and_capture_payment() {
+    let response = Stripe {}
+        .make_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::Charged);
+}
+
+#[actix_web::test]
+async fn should_capture_already_authorized_payment() {
+    let connector = Stripe {};
+    let response = connector
+        .authorize_and_capture_payment(get_payment_authorize_data(), None, None)
+        .await;
+    assert_eq!(response.unwrap().status, enums::AttemptStatus::Charged);
+}
+
+#[actix_web::test]
+async fn should_partially_capture_already_authorized_payment() {
+    let connector = Stripe {};
+    let response = connector
+        .authorize_and_capture_payment(
+            get_payment_authorize_data(),
+            Some(types::PaymentsCaptureData {
+                amount_to_capture: Some(50),
+                ..utils::PaymentCaptureType::default().0
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(response.unwrap().status, enums::AttemptStatus::Charged);
+}
+
+#[actix_web::test]
+async fn should_sync_payment() {
+    let connector = Stripe {};
+    let authorize_response = connector
+        .authorize_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    let txn_id = utils::get_connector_transaction_id(authorize_response);
+    let response = connector
+        .psync_retry_till_status_matches(
+            enums::AttemptStatus::Authorized,
+            Some(types::PaymentsSyncData {
+                connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
+                    txn_id.unwrap(),
+                ),
+                encoded_data: None,
+                capture_method: None,
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(response.status, enums::AttemptStatus::Authorized,);
+}
+
+#[actix_web::test]
+async fn should_void_already_authorized_payment() {
+    let connector = Stripe {};
+    let response = connector
+        .authorize_and_void_payment(
+            get_payment_authorize_data(),
+            Some(types::PaymentsCancelData {
+                connector_transaction_id: "".to_string(),
+                cancellation_reason: Some("requested_by_customer".to_string()),
+            }),
+            None,
+        )
+        .await;
+    assert_eq!(response.unwrap().status, enums::AttemptStatus::Voided);
+}
+
+#[actix_web::test]
+async fn should_fail_payment_for_incorrect_card_number() {
+    let response = Stripe {}
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                    card_number: Secret::new("4024007134364842".to_string()),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    let x = response.response.unwrap_err();
+    assert_eq!(
+        x.message,
+        "Your card was declined. Your request was in test mode, but used a non test (live) card. For a list of valid test cards, visit: https://stripe.com/docs/testing.",
+    );
+}
+
+#[actix_web::test]
+async fn should_fail_payment_for_no_card_number() {
+    let response = Stripe {}
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                    card_number: Secret::new("".to_string()),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    let x = response.response.unwrap_err();
+    assert_eq!(
+        x.message,
+        "You passed an empty string for 'payment_method_data[card][number]'. We assume empty values are an attempt to unset a parameter; however 'payment_method_data[card][number]' cannot be unset. You should remove 'payment_method_data[card][number]' from your request or supply a non-empty value.",
+    );
+}
+
+#[actix_web::test]
+async fn should_fail_payment_for_invalid_exp_month() {
+    let response = Stripe {}
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                    card_exp_month: Secret::new("13".to_string()),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    let x = response.response.unwrap_err();
+    assert_eq!(x.message, "Your card's expiration month is invalid.",);
+}
+
+#[actix_web::test]
+async fn should_fail_payment_for_invalid_exp_year() {
+    let response = Stripe {}
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                    card_exp_year: Secret::new("2022".to_string()),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    let x = response.response.unwrap_err();
+    assert_eq!(x.message, "Your card's expiration year is invalid.",);
+}
+
+#[actix_web::test]
+async fn should_fail_payment_for_invalid_card_cvc() {
+    let response = Stripe {}
+        .make_payment(
+            Some(types::PaymentsAuthorizeData {
+                payment_method_data: types::api::PaymentMethod::Card(api::CCard {
+                    card_cvc: Secret::new("12".to_string()),
+                    ..utils::CCardType::default().0
+                }),
+                ..utils::PaymentAuthorizeType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    let x = response.response.unwrap_err();
+    assert_eq!(x.message, "Your card's security code is invalid.",);
+}
+
+#[actix_web::test]
+async fn should_fail_capture_for_invalid_payment() {
+    let connector = Stripe {};
+    let authorize_response = connector
+        .authorize_payment(get_payment_authorize_data(), None)
+        .await
+        .unwrap();
+    assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
+    tokio::time::sleep(Duration::from_secs(5)).await; // to avoid 404 error as stripe takes some time to process the new transaction
+    let response = connector
+        .capture_payment("12345".to_string(), None, None)
+        .await
+        .unwrap();
+    let err = response.response.unwrap_err();
+    assert_eq!(err.message, "No such payment_intent: '12345'".to_string());
+    assert_eq!(err.code, "resource_missing".to_string());
+}
+
+#[actix_web::test]
+async fn should_refund_succeeded_payment() {
+    let connector = Stripe {};
+    let response = connector
+        .make_payment_and_refund(get_payment_authorize_data(), None, None)
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap().refund_status,
+        enums::RefundStatus::Success,
+    );
+}
+
+#[actix_web::test]
+async fn should_partially_refund_succeeded_payment() {
+    let connector = Stripe {};
+    let refund_response = connector
+        .make_payment_and_refund(
+            get_payment_authorize_data(),
+            Some(types::RefundsData {
+                refund_amount: 50,
+                ..utils::PaymentRefundType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        refund_response.response.unwrap().refund_status,
+        enums::RefundStatus::Success,
+    );
+}
+
+#[actix_web::test]
+async fn should_refund_succeeded_payment_multiple_times() {
+    let connector = Stripe {};
+    connector
+        .make_payment_and_multiple_refund(
+            get_payment_authorize_data(),
+            Some(types::RefundsData {
+                refund_amount: 50,
+                ..utils::PaymentRefundType::default().0
+            }),
+            None,
+        )
+        .await;
+}
+
+#[actix_web::test]
+async fn should_fail_refund_for_invalid_amount() {
+    let connector = Stripe {};
+    let response = connector
+        .make_payment_and_refund(
+            get_payment_authorize_data(),
+            Some(types::RefundsData {
+                refund_amount: 150,
+                ..utils::PaymentRefundType::default().0
+            }),
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap_err().message,
+        "Refund amount ($1.50) is greater than charge amount ($1.00)",
+    );
+}
+
+#[actix_web::test]
+async fn should_sync_refund() {
+    let connector = Stripe {};
+    let refund_response = connector
+        .make_payment_and_refund(get_payment_authorize_data(), None, None)
+        .await
+        .unwrap();
+    let response = connector
+        .rsync_retry_till_status_matches(
+            enums::RefundStatus::Success,
+            refund_response.response.unwrap().connector_refund_id,
+            None,
+            None,
+        )
+        .await
+        .unwrap();
+    assert_eq!(
+        response.response.unwrap().refund_status,
+        enums::RefundStatus::Success,
+    );
+}
diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs
index f493c2e8da2..e1cee6bd415 100644
--- a/crates/router/tests/connectors/utils.rs
+++ b/crates/router/tests/connectors/utils.rs
@@ -1,4 +1,4 @@
-use std::{fmt::Debug, marker::PhantomData, thread::sleep, time::Duration};
+use std::{fmt::Debug, marker::PhantomData, time::Duration};
 
 use async_trait::async_trait;
 use error_stack::Report;
@@ -18,6 +18,10 @@ pub trait Connector {
     fn get_connector_meta(&self) -> Option<serde_json::Value> {
         None
     }
+    /// interval in seconds to be followed when making the subsequent request whenever needed
+    fn get_request_interval(&self) -> u64 {
+        5
+    }
 }
 
 #[derive(Debug, Default, Clone)]
@@ -36,14 +40,16 @@ pub trait ConnectorActions: Connector {
     ) -> Result<types::PaymentsAuthorizeRouterData, Report<ConnectorError>> {
         let integration = self.get_data().connector.get_connector_integration();
         let request = self.generate_data(
-            payment_data.unwrap_or_else(|| types::PaymentsAuthorizeData {
+            types::PaymentsAuthorizeData {
+                confirm: false,
                 capture_method: Some(storage_models::enums::CaptureMethod::Manual),
-                ..PaymentAuthorizeType::default().0
-            }),
+                ..(payment_data.unwrap_or(PaymentAuthorizeType::default().0))
+            },
             payment_info,
         );
         call_connector(request, integration).await
     }
+
     async fn make_payment(
         &self,
         payment_data: Option<types::PaymentsAuthorizeData>,
@@ -70,25 +76,23 @@ pub trait ConnectorActions: Connector {
         call_connector(request, integration).await
     }
 
-    /// will retry the psync till the given status matches or retry max 3 times in a 10secs interval
+    /// will retry the psync till the given status matches or retry max 3 times
     async fn psync_retry_till_status_matches(
         &self,
         status: enums::AttemptStatus,
         payment_data: Option<types::PaymentsSyncData>,
         payment_info: Option<PaymentInfo>,
     ) -> Result<types::PaymentsSyncRouterData, Report<ConnectorError>> {
-        let max_try = 3;
-        let mut curr_try = 1;
-        while curr_try <= max_try {
+        let max_tries = 3;
+        for curr_try in 0..max_tries {
             let sync_res = self
                 .sync_payment(payment_data.clone(), payment_info.clone())
                 .await
                 .unwrap();
-            if (sync_res.status == status) || (curr_try == max_try) {
+            if (sync_res.status == status) || (curr_try == max_tries - 1) {
                 return Ok(sync_res);
             }
-            sleep(Duration::from_secs(10));
-            curr_try += 1;
+            tokio::time::sleep(Duration::from_secs(self.get_request_interval())).await;
         }
         Err(errors::ConnectorError::ProcessingStepFailed(None).into())
     }
@@ -101,17 +105,34 @@ pub trait ConnectorActions: Connector {
     ) -> Result<types::PaymentsCaptureRouterData, Report<ConnectorError>> {
         let integration = self.get_data().connector.get_connector_integration();
         let request = self.generate_data(
-            payment_data.unwrap_or(types::PaymentsCaptureData {
-                amount_to_capture: Some(100),
-                currency: enums::Currency::USD,
+            types::PaymentsCaptureData {
                 connector_transaction_id: transaction_id,
-                amount: 100,
-            }),
+                ..payment_data.unwrap_or(PaymentCaptureType::default().0)
+            },
             payment_info,
         );
         call_connector(request, integration).await
     }
 
+    async fn authorize_and_capture_payment(
+        &self,
+        authorize_data: Option<types::PaymentsAuthorizeData>,
+        capture_data: Option<types::PaymentsCaptureData>,
+        payment_info: Option<PaymentInfo>,
+    ) -> Result<types::PaymentsCaptureRouterData, Report<ConnectorError>> {
+        let authorize_response = self
+            .authorize_payment(authorize_data, payment_info.clone())
+            .await
+            .unwrap();
+        assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
+        let txn_id = get_connector_transaction_id(authorize_response);
+        let response = self
+            .capture_payment(txn_id.unwrap(), capture_data, payment_info)
+            .await
+            .unwrap();
+        return Ok(response);
+    }
+
     async fn void_payment(
         &self,
         transaction_id: String,
@@ -120,15 +141,35 @@ pub trait ConnectorActions: Connector {
     ) -> Result<types::PaymentsCancelRouterData, Report<ConnectorError>> {
         let integration = self.get_data().connector.get_connector_integration();
         let request = self.generate_data(
-            payment_data.unwrap_or(types::PaymentsCancelData {
+            types::PaymentsCancelData {
                 connector_transaction_id: transaction_id,
-                cancellation_reason: Some("Test cancellation".to_string()),
-            }),
+                ..payment_data.unwrap_or(PaymentCancelType::default().0)
+            },
             payment_info,
         );
         call_connector(request, integration).await
     }
 
+    async fn authorize_and_void_payment(
+        &self,
+        authorize_data: Option<types::PaymentsAuthorizeData>,
+        void_data: Option<types::PaymentsCancelData>,
+        payment_info: Option<PaymentInfo>,
+    ) -> Result<types::PaymentsCancelRouterData, Report<ConnectorError>> {
+        let authorize_response = self
+            .authorize_payment(authorize_data, payment_info.clone())
+            .await
+            .unwrap();
+        assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
+        let txn_id = get_connector_transaction_id(authorize_response);
+        tokio::time::sleep(Duration::from_secs(self.get_request_interval())).await; // to avoid 404 error
+        let response = self
+            .void_payment(txn_id.unwrap(), void_data, payment_info)
+            .await
+            .unwrap();
+        return Ok(response);
+    }
+
     async fn refund_payment(
         &self,
         transaction_id: String,
@@ -137,24 +178,70 @@ pub trait ConnectorActions: Connector {
     ) -> Result<types::RefundExecuteRouterData, Report<ConnectorError>> {
         let integration = self.get_data().connector.get_connector_integration();
         let request = self.generate_data(
-            payment_data.unwrap_or_else(|| types::RefundsData {
-                amount: 100,
-                currency: enums::Currency::USD,
-                refund_id: uuid::Uuid::new_v4().to_string(),
+            types::RefundsData {
                 connector_transaction_id: transaction_id,
-                refund_amount: 100,
-                connector_metadata: None,
-                connector_refund_id: None,
-                reason: Some("Customer returned product".to_string()),
-            }),
+                ..payment_data.unwrap_or(PaymentRefundType::default().0)
+            },
             payment_info,
         );
         call_connector(request, integration).await
     }
 
+    async fn make_payment_and_refund(
+        &self,
+        authorize_data: Option<types::PaymentsAuthorizeData>,
+        refund_data: Option<types::RefundsData>,
+        payment_info: Option<PaymentInfo>,
+    ) -> Result<types::RefundExecuteRouterData, Report<ConnectorError>> {
+        //make a successful payment
+        let response = self
+            .make_payment(authorize_data, payment_info.clone())
+            .await
+            .unwrap();
+
+        //try refund for previous payment
+        let transaction_id = get_connector_transaction_id(response).unwrap();
+        tokio::time::sleep(Duration::from_secs(self.get_request_interval())).await; // to avoid 404 error
+        Ok(self
+            .refund_payment(transaction_id, refund_data, payment_info)
+            .await
+            .unwrap())
+    }
+
+    async fn make_payment_and_multiple_refund(
+        &self,
+        authorize_data: Option<types::PaymentsAuthorizeData>,
+        refund_data: Option<types::RefundsData>,
+        payment_info: Option<PaymentInfo>,
+    ) {
+        //make a successful payment
+        let response = self
+            .make_payment(authorize_data, payment_info.clone())
+            .await
+            .unwrap();
+
+        //try refund for previous payment
+        let transaction_id = get_connector_transaction_id(response).unwrap();
+        for _x in 0..2 {
+            tokio::time::sleep(Duration::from_secs(self.get_request_interval())).await; // to avoid 404 error
+            let refund_response = self
+                .refund_payment(
+                    transaction_id.clone(),
+                    refund_data.clone(),
+                    payment_info.clone(),
+                )
+                .await
+                .unwrap();
+            assert_eq!(
+                refund_response.response.unwrap().refund_status,
+                enums::RefundStatus::Success,
+            );
+        }
+    }
+
     async fn sync_refund(
         &self,
-        transaction_id: String,
+        refund_id: String,
         payment_data: Option<types::RefundsData>,
         payment_info: Option<PaymentInfo>,
     ) -> Result<types::RefundSyncRouterData, Report<ConnectorError>> {
@@ -164,17 +251,45 @@ pub trait ConnectorActions: Connector {
                 amount: 1000,
                 currency: enums::Currency::USD,
                 refund_id: uuid::Uuid::new_v4().to_string(),
-                connector_transaction_id: transaction_id,
+                connector_transaction_id: "".to_string(),
                 refund_amount: 100,
                 connector_metadata: None,
                 reason: None,
-                connector_refund_id: None,
+                connector_refund_id: Some(refund_id),
             }),
             payment_info,
         );
         call_connector(request, integration).await
     }
 
+    /// will retry the rsync till the given status matches or retry max 3 times
+    async fn rsync_retry_till_status_matches(
+        &self,
+        status: enums::RefundStatus,
+        refund_id: String,
+        payment_data: Option<types::RefundsData>,
+        payment_info: Option<PaymentInfo>,
+    ) -> Result<types::RefundSyncRouterData, Report<ConnectorError>> {
+        let max_tries = 3;
+        for curr_try in 0..max_tries {
+            let sync_res = self
+                .sync_refund(
+                    refund_id.clone(),
+                    payment_data.clone(),
+                    payment_info.clone(),
+                )
+                .await
+                .unwrap();
+            if (sync_res.clone().response.unwrap().refund_status == status)
+                || (curr_try == max_tries - 1)
+            {
+                return Ok(sync_res);
+            }
+            tokio::time::sleep(Duration::from_secs(self.get_request_interval())).await;
+        }
+        Err(errors::ConnectorError::ProcessingStepFailed(None).into())
+    }
+
     fn generate_data<Flow, Req: From<Req>, Res>(
         &self,
         req: Req,
@@ -258,6 +373,8 @@ pub trait LocalMock {
 }
 
 pub struct PaymentAuthorizeType(pub types::PaymentsAuthorizeData);
+pub struct PaymentCaptureType(pub types::PaymentsCaptureData);
+pub struct PaymentCancelType(pub types::PaymentsCancelData);
 pub struct PaymentSyncType(pub types::PaymentsSyncData);
 pub struct PaymentRefundType(pub types::RefundsData);
 pub struct CCardType(pub api::CCard);
@@ -296,6 +413,26 @@ impl Default for PaymentAuthorizeType {
     }
 }
 
+impl Default for PaymentCaptureType {
+    fn default() -> Self {
+        Self(types::PaymentsCaptureData {
+            amount_to_capture: Some(100),
+            currency: enums::Currency::USD,
+            connector_transaction_id: "".to_string(),
+            amount: 100,
+        })
+    }
+}
+
+impl Default for PaymentCancelType {
+    fn default() -> Self {
+        Self(types::PaymentsCancelData {
+            cancellation_reason: Some("requested_by_customer".to_string()),
+            connector_transaction_id: "".to_string(),
+        })
+    }
+}
+
 impl Default for BrowserInfoType {
     fn default() -> Self {
         let data = types::BrowserInformation {
@@ -330,13 +467,13 @@ impl Default for PaymentSyncType {
 impl Default for PaymentRefundType {
     fn default() -> Self {
         let data = types::RefundsData {
-            amount: 1000,
+            amount: 100,
             currency: enums::Currency::USD,
             refund_id: uuid::Uuid::new_v4().to_string(),
             connector_transaction_id: String::new(),
             refund_amount: 100,
             connector_metadata: None,
-            reason: None,
+            reason: Some("Customer returned product".to_string()),
             connector_refund_id: None,
         };
         Self(data)

## 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 -->
This PR adds tests for stripe connector.

### 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
<!--
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 its an obvious bug or documentation fix
that will have little conversation).
-->
Closes #468.

## 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)?
-->
<img width="942" alt="Screenshot 2023-01-27 at 6 51 43 PM" src="https://user-images.githubusercontent.com/20727598/215100275-0c858b8e-5dad-4779-bbf9-4b5b385b308a.png">


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