Bug: [ENHANCEMENT] update API schema for payouts



### Bug Description

API request / response structure for payouts API hasn't kept up the development pace w payments API. Overtime, some of the fields in payouts API request have been obsoleted, whereas some of the new fields / objects are added for consuming different features.

On top of this, the auto-generated API docs are not well maintained.

### Expected Behavior

Loose fields like email, name etc. (revolving around customer's context) are to be moved to the `customer` object

Generated API reference + docs are to be reviewed and updated as necessary.


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

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

### Have you read the Contributing Guidelines?

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

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/crates/api_models/src/payouts.rs b/crates/api_models/src/payouts.rs
index c3ae760215b..3333fb6f546 100644
--- a/crates/api_models/src/payouts.rs
+++ b/crates/api_models/src/payouts.rs
@@ -18,7 +18,10 @@ pub enum PayoutRequest {
     PayoutRetrieveRequest(PayoutRetrieveRequest),
 }
 
-#[derive(Default, Debug, Deserialize, Serialize, Clone, ToSchema)]
+#[derive(
+    Default, Debug, Deserialize, Serialize, Clone, ToSchema, router_derive::PolymorphicSchema,
+)]
+#[generate_schemas(PayoutsCreateRequest, PayoutUpdateRequest, PayoutConfirmRequest)]
 #[serde(deny_unknown_fields)]
 pub struct PayoutCreateRequest {
     /// Unique identifier for the payout. This ensures idempotency for multiple payouts that have been done by a single merchant. This field is auto generated and is returned in the API response, **not required to be included in the Payout Create/Update Request.**
@@ -26,21 +29,27 @@ pub struct PayoutCreateRequest {
         value_type = Option<String>,
         min_length = 30,
         max_length = 30,
-        example = "payout_mbabizu24mvu3mela5njyhpit4"
+        example = "187282ab-40ef-47a9-9206-5099ba31e432"
     )]
+    #[remove_in(PayoutsCreateRequest, PayoutUpdateRequest, PayoutConfirmRequest)]
     pub payout_id: Option<String>, // TODO: #1321 https://github.com/juspay/hyperswitch/issues/1321
 
     /// This is an identifier for the merchant account. This is inferred from the API key provided during the request, **not required to be included in the Payout Create/Update Request.**
     #[schema(max_length = 255, value_type = Option<String>, example = "merchant_1668273825")]
+    #[remove_in(PayoutsCreateRequest, PayoutUpdateRequest, PayoutConfirmRequest)]
     pub merchant_id: Option<id_type::MerchantId>,
 
     /// The payout amount. Amount for the payout in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,
-    #[schema(value_type = i64, example = 1000)]
+    #[schema(value_type = Option<u64>, example = 1000)]
+    #[mandatory_in(PayoutsCreateRequest = u64)]
+    #[remove_in(PayoutsConfirmRequest)]
     #[serde(default, deserialize_with = "payments::amount::deserialize_option")]
     pub amount: Option<payments::Amount>,
 
     /// The currency of the payout request can be specified here
-    #[schema(value_type = Currency, example = "USD")]
+    #[schema(value_type = Option<Currency>, example = "USD")]
+    #[mandatory_in(PayoutsCreateRequest = Currency)]
+    #[remove_in(PayoutsConfirmRequest)]
     pub currency: Option<api_enums::Currency>,
 
     /// Specifies routing algorithm for selecting a connector
@@ -55,11 +64,12 @@ pub struct PayoutCreateRequest {
     pub connector: Option<Vec<api_enums::PayoutConnectors>>,
 
     /// This field is used when merchant wants to confirm the payout, thus useful for the payout _Confirm_ request. Ideally merchants should _Create_ a payout, _Update_ it (if required), then _Confirm_ it.
-    #[schema(value_type = bool, example = true, default = false)]
+    #[schema(value_type = Option<bool>, example = true, default = false)]
+    #[remove_in(PayoutConfirmRequest)]
     pub confirm: Option<bool>,
 
     /// The payout_type of the payout request can be specified here, this is a mandatory field to _Confirm_ the payout, i.e., should be passed in _Create_ request, if not then should be updated in the payout _Update_ request, then only it can be confirmed.
-    #[schema(value_type = PayoutType, example = "card")]
+    #[schema(value_type = Option<PayoutType>, example = "card")]
     pub payout_type: Option<api_enums::PayoutType>,
 
     /// The payout method information required for carrying out a payout
@@ -67,7 +77,7 @@ pub struct PayoutCreateRequest {
     pub payout_method_data: Option<PayoutMethodData>,
 
     /// The billing address for the payout
-    #[schema(value_type = Option<Object>, example = json!(r#"{
+    #[schema(value_type = Option<Address>, example = json!(r#"{
         "address": {
             "line1": "1467",
             "line2": "Harrison Street",
@@ -83,52 +93,42 @@ pub struct PayoutCreateRequest {
     }"#))]
     pub billing: Option<payments::Address>,
 
-    /// The identifier for the customer object. If not provided the customer ID will be autogenerated.
-    #[schema(value_type = Option<String>, max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
-    pub customer_id: Option<id_type::CustomerId>,
-
     /// Set to true to confirm the payout without review, no further action required
-    #[schema(value_type = bool, example = true, default = false)]
+    #[schema(value_type = Option<bool>, example = true, default = false)]
     pub auto_fulfill: Option<bool>,
 
-    /// description: The customer's email address
-    #[schema(max_length = 255, value_type = Option<String>, example = "johntest@test.com")]
-    pub email: Option<Email>,
-
-    /// description: The customer's name
-    #[schema(value_type = Option<String>, max_length = 255, example = "John Test")]
-    pub name: Option<Secret<String>>,
-
-    /// The customer's phone number
-    #[schema(value_type = Option<String>, max_length = 255, example = "9123456789")]
-    pub phone: Option<Secret<String>>,
+    /// The identifier for the customer object. If not provided the customer ID will be autogenerated. _Deprecated: Use customer_id instead._
+    #[schema(deprecated, value_type = Option<String>, max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
+    pub customer_id: Option<id_type::CustomerId>,
 
-    /// The country code for the customer phone number
-    #[schema(max_length = 255, example = "+1")]
-    pub phone_country_code: Option<String>,
+    /// Passing this object creates a new customer or attaches an existing customer to the payout
+    #[schema(value_type = Option<CustomerDetails>)]
+    pub customer: Option<payments::CustomerDetails>,
 
     /// It's a token used for client side verification.
-    #[schema(value_type = String, example = "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo")]
+    #[schema(value_type = Option<String>, example = "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo")]
+    #[remove_in(PayoutsCreateRequest)]
+    #[mandatory_in(PayoutConfirmRequest = String)]
     pub client_secret: Option<String>,
 
     /// The URL to redirect after the completion of the operation
-    #[schema(value_type = String, example = "https://hyperswitch.io")]
+    #[schema(value_type = Option<String>, example = "https://hyperswitch.io")]
     pub return_url: Option<String>,
 
-    /// Business country of the merchant for this payout
-    #[schema(example = "US", value_type = CountryAlpha2)]
+    /// Business country of the merchant for this payout. _Deprecated: Use profile_id instead._
+    #[schema(deprecated, example = "US", value_type = Option<CountryAlpha2>)]
     pub business_country: Option<api_enums::CountryAlpha2>,
 
-    /// Business label of the merchant for this payout
-    #[schema(example = "food", value_type = Option<String>)]
+    /// Business label of the merchant for this payout. _Deprecated: Use profile_id instead._
+    #[schema(deprecated, example = "food", value_type = Option<String>)]
     pub business_label: Option<String>,
 
     /// A description of the payout
-    #[schema(example = "It's my first payout request", value_type = String)]
+    #[schema(example = "It's my first payout request", value_type = Option<String>)]
     pub description: Option<String>,
 
     /// Type of entity to whom the payout is being carried out to, select from the given list of options
-    #[schema(value_type = PayoutEntityType, example = "Individual")]
+    #[schema(value_type = Option<PayoutEntityType>, example = "Individual")]
     pub entity_type: Option<api_enums::PayoutEntityType>,
 
     /// Specifies whether or not the payout request is recurring
@@ -140,18 +140,18 @@ pub struct PayoutCreateRequest {
     pub metadata: Option<pii::SecretSerdeValue>,
 
     /// Provide a reference to a stored payout method, used to process the payout.
-    #[schema(example = "187282ab-40ef-47a9-9206-5099ba31e432")]
+    #[schema(example = "187282ab-40ef-47a9-9206-5099ba31e432", value_type = Option<String>)]
     pub payout_token: Option<String>,
 
     /// The business profile to use for this payout, especially if there are multiple business profiles associated with the account, otherwise default business profile associated with the merchant account will be used.
     pub profile_id: Option<String>,
 
     /// The send method which will be required for processing payouts, check options for better understanding.
-    #[schema(value_type = PayoutSendPriority, example = "instant")]
+    #[schema(value_type = Option<PayoutSendPriority>, example = "instant")]
     pub priority: Option<api_enums::PayoutSendPriority>,
 
     /// Whether to get the payout link (if applicable). Merchant need to specify this during the Payout _Create_, this field can not be updated during Payout _Update_.
-    #[schema(default = false, example = true)]
+    #[schema(default = false, example = true, value_type = Option<bool>)]
     pub payout_link: Option<bool>,
 
     /// Custom payout link config for the particular payout, if payout link is to be generated.
@@ -162,6 +162,30 @@ pub struct PayoutCreateRequest {
     /// (900) for 15 mins
     #[schema(value_type = Option<u32>, example = 900)]
     pub session_expiry: Option<u32>,
+
+    /// Customer's email. _Deprecated: Use customer object instead._
+    #[schema(deprecated, max_length = 255, value_type = Option<String>, example = "johntest@test.com")]
+    pub email: Option<Email>,
+
+    /// Customer's name. _Deprecated: Use customer object instead._
+    #[schema(deprecated, value_type = Option<String>, max_length = 255, example = "John Test")]
+    pub name: Option<Secret<String>>,
+
+    /// Customer's phone. _Deprecated: Use customer object instead._
+    #[schema(deprecated, value_type = Option<String>, max_length = 255, example = "9123456789")]
+    pub phone: Option<Secret<String>>,
+
+    /// Customer's phone country code. _Deprecated: Use customer object instead._
+    #[schema(deprecated, max_length = 255, example = "+1")]
+    pub phone_country_code: Option<String>,
+}
+
+impl PayoutCreateRequest {
+    pub fn get_customer_id(&self) -> Option<&id_type::CustomerId> {
+        self.customer_id
+            .as_ref()
+            .or(self.customer.as_ref().map(|customer| &customer.id))
+    }
 }
 
 /// Custom payout link config for the particular payout, if payout link is to be generated.
@@ -354,7 +378,7 @@ pub struct PayoutCreateResponse {
         value_type = String,
         min_length = 30,
         max_length = 30,
-        example = "payout_mbabizu24mvu3mela5njyhpit4"
+        example = "187282ab-40ef-47a9-9206-5099ba31e432"
     )]
     pub payout_id: String, // TODO: Update this to PayoutIdType similar to PaymentIdType
 
@@ -396,29 +420,17 @@ pub struct PayoutCreateResponse {
     }"#))]
     pub billing: Option<payments::Address>,
 
-    /// The identifier for the customer object. If not provided the customer ID will be autogenerated.
-    #[schema(value_type = String, max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
-    pub customer_id: id_type::CustomerId,
-
     /// Set to true to confirm the payout without review, no further action required
     #[schema(value_type = bool, example = true, default = false)]
     pub auto_fulfill: bool,
 
-    /// description: The customer's email address
-    #[schema(max_length = 255, value_type = Option<String>, example = "johntest@test.com")]
-    pub email: crypto::OptionalEncryptableEmail,
-
-    /// description: The customer's name
-    #[schema(value_type = Option<String>, max_length = 255, example = "John Test")]
-    pub name: crypto::OptionalEncryptableName,
-
-    /// The customer's phone number
-    #[schema(value_type = Option<String>, max_length = 255, example = "9123456789")]
-    pub phone: crypto::OptionalEncryptablePhone,
+    /// The identifier for the customer object. If not provided the customer ID will be autogenerated.
+    #[schema(value_type = String, max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
+    pub customer_id: Option<id_type::CustomerId>,
 
-    /// The country code for the customer phone number
-    #[schema(max_length = 255, example = "+1")]
-    pub phone_country_code: Option<String>,
+    /// Passing this object creates a new customer or attaches an existing customer to the payout
+    #[schema(value_type = Option<CustomerDetailsResponse>)]
+    pub customer: Option<payments::CustomerDetailsResponse>,
 
     /// It's a token used for client side verification.
     #[schema(value_type = String, example = "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo")]
@@ -437,7 +449,7 @@ pub struct PayoutCreateResponse {
     pub business_label: Option<String>,
 
     /// A description of the payout
-    #[schema(example = "It's my first payout request", value_type = String)]
+    #[schema(example = "It's my first payout request", value_type = Option<String>)]
     pub description: Option<String>,
 
     /// Type of entity to whom the payout is being carried out to
@@ -445,7 +457,7 @@ pub struct PayoutCreateResponse {
     pub entity_type: api_enums::PayoutEntityType,
 
     /// Specifies whether or not the payout request is recurring
-    #[schema(value_type = Option<bool>, default = false)]
+    #[schema(value_type = bool, default = false)]
     pub recurring: bool,
 
     /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
@@ -453,15 +465,15 @@ pub struct PayoutCreateResponse {
     pub metadata: Option<pii::SecretSerdeValue>,
 
     /// Current status of the Payout
-    #[schema(value_type = PayoutStatus, example = Pending)]
+    #[schema(value_type = PayoutStatus, example = RequiresConfirmation)]
     pub status: api_enums::PayoutStatus,
 
     /// If there was an error while calling the connector the error message is received here
-    #[schema(value_type = String, example = "Failed while verifying the card")]
+    #[schema(value_type = Option<String>, example = "Failed while verifying the card")]
     pub error_message: Option<String>,
 
     /// If there was an error while calling the connectors the code is received here
-    #[schema(value_type = String, example = "E0001")]
+    #[schema(value_type = Option<String>, example = "E0001")]
     pub error_code: Option<String>,
 
     /// The business profile that is associated with this payout
@@ -488,6 +500,22 @@ pub struct PayoutCreateResponse {
     /// If payout link was requested, this contains the link's ID and the URL to render the payout widget
     #[schema(value_type = Option<PayoutLinkResponse>)]
     pub payout_link: Option<PayoutLinkResponse>,
+
+    /// Customer's email. _Deprecated: Use customer object instead._
+    #[schema(deprecated, max_length = 255, value_type = Option<String>, example = "johntest@test.com")]
+    pub email: crypto::OptionalEncryptableEmail,
+
+    /// Customer's name. _Deprecated: Use customer object instead._
+    #[schema(deprecated, value_type = Option<String>, max_length = 255, example = "John Test")]
+    pub name: crypto::OptionalEncryptableName,
+
+    /// Customer's phone. _Deprecated: Use customer object instead._
+    #[schema(deprecated, value_type = Option<String>, max_length = 255, example = "9123456789")]
+    pub phone: crypto::OptionalEncryptablePhone,
+
+    /// Customer's phone country code. _Deprecated: Use customer object instead._
+    #[schema(deprecated, max_length = 255, example = "+1")]
+    pub phone_country_code: Option<String>,
 }
 
 #[derive(
@@ -542,7 +570,7 @@ pub struct PayoutRetrieveRequest {
         value_type = String,
         min_length = 30,
         max_length = 30,
-        example = "payout_mbabizu24mvu3mela5njyhpit4"
+        example = "187282ab-40ef-47a9-9206-5099ba31e432"
     )]
     pub payout_id: String,
 
@@ -556,7 +584,10 @@ pub struct PayoutRetrieveRequest {
     pub merchant_id: Option<id_type::MerchantId>,
 }
 
-#[derive(Default, Debug, Serialize, ToSchema, Clone, Deserialize)]
+#[derive(
+    Default, Debug, Deserialize, Serialize, Clone, ToSchema, router_derive::PolymorphicSchema,
+)]
+#[generate_schemas(PayoutCancelRequest, PayoutFulfillRequest)]
 pub struct PayoutActionRequest {
     /// Unique identifier for the payout. This ensures idempotency for multiple payouts
     /// that have been done by a single merchant. This field is auto generated and is returned in the API response.
@@ -564,8 +595,9 @@ pub struct PayoutActionRequest {
         value_type = String,
         min_length = 30,
         max_length = 30,
-        example = "payout_mbabizu24mvu3mela5njyhpit4"
+        example = "187282ab-40ef-47a9-9206-5099ba31e432"
     )]
+    #[serde(skip_deserializing)]
     pub payout_id: String,
 }
 
@@ -645,7 +677,7 @@ pub struct PayoutListFilterConstraints {
     value_type = Option<String>,
     min_length = 30,
     max_length = 30,
-    example = "payout_mbabizu24mvu3mela5njyhpit4"
+    example = "187282ab-40ef-47a9-9206-5099ba31e432"
 )]
     pub payout_id: Option<String>,
     /// The identifier for business profile
@@ -687,15 +719,19 @@ pub struct PayoutListResponse {
     pub data: Vec<PayoutCreateResponse>,
 }
 
-#[derive(Clone, Debug, serde::Serialize)]
+#[derive(Clone, Debug, serde::Serialize, ToSchema)]
 pub struct PayoutListFilters {
     /// The list of available connector filters
+    #[schema(value_type = Vec<PayoutConnectors>)]
     pub connector: Vec<api_enums::PayoutConnectors>,
     /// The list of available currency filters
+    #[schema(value_type = Vec<Currency>)]
     pub currency: Vec<common_enums::Currency>,
     /// The list of available payout status filters
+    #[schema(value_type = Vec<PayoutStatus>)]
     pub status: Vec<common_enums::PayoutStatus>,
     /// The list of available payout method filters
+    #[schema(value_type = Vec<PayoutType>)]
     pub payout_method: Vec<common_enums::PayoutType>,
 }
 
diff --git a/crates/diesel_models/src/payout_attempt.rs b/crates/diesel_models/src/payout_attempt.rs
index d8e5af55ccc..fe8bbd77a38 100644
--- a/crates/diesel_models/src/payout_attempt.rs
+++ b/crates/diesel_models/src/payout_attempt.rs
@@ -11,9 +11,9 @@ use crate::{enums as storage_enums, schema::payout_attempt};
 pub struct PayoutAttempt {
     pub payout_attempt_id: String,
     pub payout_id: String,
-    pub customer_id: common_utils::id_type::CustomerId,
+    pub customer_id: Option<common_utils::id_type::CustomerId>,
     pub merchant_id: common_utils::id_type::MerchantId,
-    pub address_id: String,
+    pub address_id: Option<String>,
     pub connector: Option<String>,
     pub connector_payout_id: Option<String>,
     pub payout_token: Option<String>,
@@ -47,9 +47,9 @@ pub struct PayoutAttempt {
 pub struct PayoutAttemptNew {
     pub payout_attempt_id: String,
     pub payout_id: String,
-    pub customer_id: common_utils::id_type::CustomerId,
+    pub customer_id: Option<common_utils::id_type::CustomerId>,
     pub merchant_id: common_utils::id_type::MerchantId,
-    pub address_id: String,
+    pub address_id: Option<String>,
     pub connector: Option<String>,
     pub connector_payout_id: Option<String>,
     pub payout_token: Option<String>,
@@ -83,6 +83,8 @@ pub enum PayoutAttemptUpdate {
     BusinessUpdate {
         business_country: Option<storage_enums::CountryAlpha2>,
         business_label: Option<String>,
+        address_id: Option<String>,
+        customer_id: Option<common_utils::id_type::CustomerId>,
     },
     UpdateRouting {
         connector: String,
@@ -104,6 +106,8 @@ pub struct PayoutAttemptUpdateInternal {
     pub connector: Option<String>,
     pub routing_info: Option<serde_json::Value>,
     pub last_modified_at: PrimitiveDateTime,
+    pub address_id: Option<String>,
+    pub customer_id: Option<common_utils::id_type::CustomerId>,
 }
 
 impl Default for PayoutAttemptUpdateInternal {
@@ -120,6 +124,8 @@ impl Default for PayoutAttemptUpdateInternal {
             connector: None,
             routing_info: None,
             last_modified_at: common_utils::date_time::now(),
+            address_id: None,
+            customer_id: None,
         }
     }
 }
@@ -148,9 +154,13 @@ impl From<PayoutAttemptUpdate> for PayoutAttemptUpdateInternal {
             PayoutAttemptUpdate::BusinessUpdate {
                 business_country,
                 business_label,
+                address_id,
+                customer_id,
             } => Self {
                 business_country,
                 business_label,
+                address_id,
+                customer_id,
                 ..Default::default()
             },
             PayoutAttemptUpdate::UpdateRouting {
@@ -179,6 +189,8 @@ impl PayoutAttemptUpdate {
             connector,
             routing_info,
             last_modified_at,
+            address_id,
+            customer_id,
         } = self.into();
         PayoutAttempt {
             payout_token: payout_token.or(source.payout_token),
@@ -192,6 +204,8 @@ impl PayoutAttemptUpdate {
             connector: connector.or(source.connector),
             routing_info: routing_info.or(source.routing_info),
             last_modified_at,
+            address_id: address_id.or(source.address_id),
+            customer_id: customer_id.or(source.customer_id),
             ..source
         }
     }
diff --git a/crates/diesel_models/src/payouts.rs b/crates/diesel_models/src/payouts.rs
index 9573d056641..cb8f6f9a884 100644
--- a/crates/diesel_models/src/payouts.rs
+++ b/crates/diesel_models/src/payouts.rs
@@ -13,8 +13,8 @@ use crate::{enums as storage_enums, schema::payouts};
 pub struct Payouts {
     pub payout_id: String,
     pub merchant_id: common_utils::id_type::MerchantId,
-    pub customer_id: common_utils::id_type::CustomerId,
-    pub address_id: String,
+    pub customer_id: Option<common_utils::id_type::CustomerId>,
+    pub address_id: Option<String>,
     pub payout_type: Option<storage_enums::PayoutType>,
     pub payout_method_id: Option<String>,
     pub amount: MinorUnit,
@@ -54,8 +54,8 @@ pub struct Payouts {
 pub struct PayoutsNew {
     pub payout_id: String,
     pub merchant_id: common_utils::id_type::MerchantId,
-    pub customer_id: common_utils::id_type::CustomerId,
-    pub address_id: String,
+    pub customer_id: Option<common_utils::id_type::CustomerId>,
+    pub address_id: Option<String>,
     pub payout_type: Option<storage_enums::PayoutType>,
     pub payout_method_id: Option<String>,
     pub amount: MinorUnit,
@@ -96,6 +96,8 @@ pub enum PayoutsUpdate {
         status: Option<storage_enums::PayoutStatus>,
         confirm: Option<bool>,
         payout_type: Option<storage_enums::PayoutType>,
+        address_id: Option<String>,
+        customer_id: Option<common_utils::id_type::CustomerId>,
     },
     PayoutMethodIdUpdate {
         payout_method_id: String,
@@ -130,6 +132,8 @@ pub struct PayoutsUpdateInternal {
     pub attempt_count: Option<i16>,
     pub confirm: Option<bool>,
     pub payout_type: Option<common_enums::PayoutType>,
+    pub address_id: Option<String>,
+    pub customer_id: Option<common_utils::id_type::CustomerId>,
 }
 
 impl Default for PayoutsUpdateInternal {
@@ -151,6 +155,8 @@ impl Default for PayoutsUpdateInternal {
             attempt_count: None,
             confirm: None,
             payout_type: None,
+            address_id: None,
+            customer_id: None,
         }
     }
 }
@@ -172,6 +178,8 @@ impl From<PayoutsUpdate> for PayoutsUpdateInternal {
                 status,
                 confirm,
                 payout_type,
+                address_id,
+                customer_id,
             } => Self {
                 amount: Some(amount),
                 destination_currency: Some(destination_currency),
@@ -186,6 +194,8 @@ impl From<PayoutsUpdate> for PayoutsUpdateInternal {
                 status,
                 confirm,
                 payout_type,
+                address_id,
+                customer_id,
                 ..Default::default()
             },
             PayoutsUpdate::PayoutMethodIdUpdate { payout_method_id } => Self {
@@ -227,6 +237,8 @@ impl PayoutsUpdate {
             attempt_count,
             confirm,
             payout_type,
+            address_id,
+            customer_id,
         } = self.into();
         Payouts {
             amount: amount.unwrap_or(source.amount),
@@ -245,6 +257,8 @@ impl PayoutsUpdate {
             attempt_count: attempt_count.unwrap_or(source.attempt_count),
             confirm: confirm.or(source.confirm),
             payout_type: payout_type.or(source.payout_type),
+            address_id: address_id.or(source.address_id),
+            customer_id: customer_id.or(source.customer_id),
             ..source
         }
     }
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index ebeff9c97f3..08a4e68c883 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -997,11 +997,11 @@ diesel::table! {
         #[max_length = 64]
         payout_id -> Varchar,
         #[max_length = 64]
-        customer_id -> Varchar,
+        customer_id -> Nullable<Varchar>,
         #[max_length = 64]
         merchant_id -> Varchar,
         #[max_length = 64]
-        address_id -> Varchar,
+        address_id -> Nullable<Varchar>,
         #[max_length = 64]
         connector -> Nullable<Varchar>,
         #[max_length = 128]
@@ -1036,9 +1036,9 @@ diesel::table! {
         #[max_length = 64]
         merchant_id -> Varchar,
         #[max_length = 64]
-        customer_id -> Varchar,
+        customer_id -> Nullable<Varchar>,
         #[max_length = 64]
-        address_id -> Varchar,
+        address_id -> Nullable<Varchar>,
         payout_type -> Nullable<PayoutType>,
         #[max_length = 64]
         payout_method_id -> Nullable<Varchar>,
diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs
index 68c066288fd..b5cfc00ae18 100644
--- a/crates/diesel_models/src/schema_v2.rs
+++ b/crates/diesel_models/src/schema_v2.rs
@@ -980,11 +980,11 @@ diesel::table! {
         #[max_length = 64]
         payout_id -> Varchar,
         #[max_length = 64]
-        customer_id -> Varchar,
+        customer_id -> Nullable<Varchar>,
         #[max_length = 64]
         merchant_id -> Varchar,
         #[max_length = 64]
-        address_id -> Varchar,
+        address_id -> Nullable<Varchar>,
         #[max_length = 64]
         connector -> Nullable<Varchar>,
         #[max_length = 128]
@@ -1019,9 +1019,9 @@ diesel::table! {
         #[max_length = 64]
         merchant_id -> Varchar,
         #[max_length = 64]
-        customer_id -> Varchar,
+        customer_id -> Nullable<Varchar>,
         #[max_length = 64]
-        address_id -> Varchar,
+        address_id -> Nullable<Varchar>,
         payout_type -> Nullable<PayoutType>,
         #[max_length = 64]
         payout_method_id -> Nullable<Varchar>,
diff --git a/crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs b/crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs
index fe1cf4c9bdf..adba454c798 100644
--- a/crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs
+++ b/crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs
@@ -1,6 +1,6 @@
 use api_models::enums::PayoutConnectors;
 use common_enums as storage_enums;
-use common_utils::{generate_customer_id_of_default_length, id_type};
+use common_utils::id_type;
 use serde::{Deserialize, Serialize};
 use storage_enums::MerchantStorageScheme;
 use time::PrimitiveDateTime;
@@ -59,9 +59,9 @@ pub struct PayoutListFilters {
 pub struct PayoutAttempt {
     pub payout_attempt_id: String,
     pub payout_id: String,
-    pub customer_id: id_type::CustomerId,
+    pub customer_id: Option<id_type::CustomerId>,
     pub merchant_id: id_type::MerchantId,
-    pub address_id: String,
+    pub address_id: Option<String>,
     pub connector: Option<String>,
     pub connector_payout_id: Option<String>,
     pub payout_token: Option<String>,
@@ -84,9 +84,9 @@ pub struct PayoutAttempt {
 pub struct PayoutAttemptNew {
     pub payout_attempt_id: String,
     pub payout_id: String,
-    pub customer_id: id_type::CustomerId,
+    pub customer_id: Option<id_type::CustomerId>,
     pub merchant_id: id_type::MerchantId,
-    pub address_id: String,
+    pub address_id: Option<String>,
     pub connector: Option<String>,
     pub connector_payout_id: Option<String>,
     pub payout_token: Option<String>,
@@ -110,17 +110,17 @@ impl Default for PayoutAttemptNew {
         Self {
             payout_attempt_id: String::default(),
             payout_id: String::default(),
-            customer_id: generate_customer_id_of_default_length(),
+            customer_id: None,
             merchant_id: id_type::MerchantId::default(),
-            address_id: String::default(),
+            address_id: None,
             connector: None,
-            connector_payout_id: Some(String::default()),
+            connector_payout_id: None,
             payout_token: None,
             status: storage_enums::PayoutStatus::default(),
             is_eligible: None,
             error_message: None,
             error_code: None,
-            business_country: Some(storage_enums::CountryAlpha2::default()),
+            business_country: None,
             business_label: None,
             created_at: Some(now),
             last_modified_at: Some(now),
@@ -146,6 +146,8 @@ pub enum PayoutAttemptUpdate {
     BusinessUpdate {
         business_country: Option<storage_enums::CountryAlpha2>,
         business_label: Option<String>,
+        address_id: Option<String>,
+        customer_id: Option<id_type::CustomerId>,
     },
     UpdateRouting {
         connector: String,
@@ -165,6 +167,8 @@ pub struct PayoutAttemptUpdateInternal {
     pub business_label: Option<String>,
     pub connector: Option<String>,
     pub routing_info: Option<serde_json::Value>,
+    pub address_id: Option<String>,
+    pub customer_id: Option<id_type::CustomerId>,
 }
 
 impl From<PayoutAttemptUpdate> for PayoutAttemptUpdateInternal {
@@ -191,9 +195,13 @@ impl From<PayoutAttemptUpdate> for PayoutAttemptUpdateInternal {
             PayoutAttemptUpdate::BusinessUpdate {
                 business_country,
                 business_label,
+                address_id,
+                customer_id,
             } => Self {
                 business_country,
                 business_label,
+                address_id,
+                customer_id,
                 ..Default::default()
             },
             PayoutAttemptUpdate::UpdateRouting {
diff --git a/crates/hyperswitch_domain_models/src/payouts/payouts.rs b/crates/hyperswitch_domain_models/src/payouts/payouts.rs
index 16b4bf7f912..0c0fbe8ae28 100644
--- a/crates/hyperswitch_domain_models/src/payouts/payouts.rs
+++ b/crates/hyperswitch_domain_models/src/payouts/payouts.rs
@@ -54,7 +54,7 @@ pub trait PayoutsInterface {
         _filters: &PayoutFetchConstraints,
         _storage_scheme: MerchantStorageScheme,
     ) -> error_stack::Result<
-        Vec<(Payouts, PayoutAttempt, diesel_models::Customer)>,
+        Vec<(Payouts, PayoutAttempt, Option<diesel_models::Customer>)>,
         errors::StorageError,
     >;
 
@@ -71,8 +71,8 @@ pub trait PayoutsInterface {
 pub struct Payouts {
     pub payout_id: String,
     pub merchant_id: id_type::MerchantId,
-    pub customer_id: id_type::CustomerId,
-    pub address_id: String,
+    pub customer_id: Option<id_type::CustomerId>,
+    pub address_id: Option<String>,
     pub payout_type: Option<storage_enums::PayoutType>,
     pub payout_method_id: Option<String>,
     pub amount: MinorUnit,
@@ -99,8 +99,8 @@ pub struct Payouts {
 pub struct PayoutsNew {
     pub payout_id: String,
     pub merchant_id: id_type::MerchantId,
-    pub customer_id: id_type::CustomerId,
-    pub address_id: String,
+    pub customer_id: Option<id_type::CustomerId>,
+    pub address_id: Option<String>,
     pub payout_type: Option<storage_enums::PayoutType>,
     pub payout_method_id: Option<String>,
     pub amount: MinorUnit,
@@ -130,27 +130,27 @@ impl Default for PayoutsNew {
         Self {
             payout_id: String::default(),
             merchant_id: id_type::MerchantId::default(),
-            customer_id: common_utils::generate_customer_id_of_default_length(),
-            address_id: String::default(),
-            payout_type: Some(storage_enums::PayoutType::default()),
-            payout_method_id: Option::default(),
+            customer_id: None,
+            address_id: None,
+            payout_type: None,
+            payout_method_id: None,
             amount: MinorUnit::new(i64::default()),
             destination_currency: storage_enums::Currency::default(),
             source_currency: storage_enums::Currency::default(),
-            description: Option::default(),
+            description: None,
             recurring: bool::default(),
             auto_fulfill: bool::default(),
             return_url: None,
             entity_type: storage_enums::PayoutEntityType::default(),
-            metadata: Option::default(),
+            metadata: None,
             created_at: Some(now),
             last_modified_at: Some(now),
             attempt_count: 1,
             profile_id: String::default(),
             status: storage_enums::PayoutStatus::default(),
             confirm: None,
-            payout_link_id: Option::default(),
-            client_secret: Option::default(),
+            payout_link_id: None,
+            client_secret: None,
             priority: None,
         }
     }
@@ -172,6 +172,8 @@ pub enum PayoutsUpdate {
         status: Option<storage_enums::PayoutStatus>,
         confirm: Option<bool>,
         payout_type: Option<storage_enums::PayoutType>,
+        address_id: Option<String>,
+        customer_id: Option<id_type::CustomerId>,
     },
     PayoutMethodIdUpdate {
         payout_method_id: String,
@@ -204,6 +206,8 @@ pub struct PayoutsUpdateInternal {
     pub attempt_count: Option<i16>,
     pub confirm: Option<bool>,
     pub payout_type: Option<common_enums::PayoutType>,
+    pub address_id: Option<String>,
+    pub customer_id: Option<id_type::CustomerId>,
 }
 
 impl From<PayoutsUpdate> for PayoutsUpdateInternal {
@@ -223,6 +227,8 @@ impl From<PayoutsUpdate> for PayoutsUpdateInternal {
                 status,
                 confirm,
                 payout_type,
+                address_id,
+                customer_id,
             } => Self {
                 amount: Some(amount),
                 destination_currency: Some(destination_currency),
@@ -237,6 +243,8 @@ impl From<PayoutsUpdate> for PayoutsUpdateInternal {
                 status,
                 confirm,
                 payout_type,
+                address_id,
+                customer_id,
                 ..Default::default()
             },
             PayoutsUpdate::PayoutMethodIdUpdate { payout_method_id } => Self {
diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs
index 50bbd797051..cd51a44edf7 100644
--- a/crates/openapi/src/openapi.rs
+++ b/crates/openapi/src/openapi.rs
@@ -172,8 +172,9 @@ Never share your secret api keys. Keep them guarded and secure.
         routes::payouts::payouts_cancel,
         routes::payouts::payouts_fulfill,
         routes::payouts::payouts_list,
-        routes::payouts::payouts_filter,
         routes::payouts::payouts_confirm,
+        routes::payouts::payouts_list_filters,
+        routes::payouts::payouts_list_by_filter,
 
         // Routes for api keys
         routes::api_keys::api_key_create,
@@ -479,16 +480,19 @@ Never share your secret api keys. Keep them guarded and secure.
         api_models::payouts::BacsBankTransfer,
         api_models::payouts::SepaBankTransfer,
         api_models::payouts::PixBankTransfer,
-        api_models::payouts::PayoutRequest,
+        api_models::payouts::PayoutsCreateRequest,
+        api_models::payouts::PayoutUpdateRequest,
+        api_models::payouts::PayoutConfirmRequest,
+        api_models::payouts::PayoutCancelRequest,
+        api_models::payouts::PayoutFulfillRequest,
+        api_models::payouts::PayoutRetrieveRequest,
         api_models::payouts::PayoutAttemptResponse,
-        api_models::payouts::PayoutActionRequest,
-        api_models::payouts::PayoutCreateRequest,
         api_models::payouts::PayoutCreateResponse,
         api_models::payouts::PayoutListConstraints,
+        api_models::payouts::PayoutListFilters,
         api_models::payouts::PayoutListFilterConstraints,
         api_models::payouts::PayoutListResponse,
         api_models::payouts::PayoutRetrieveBody,
-        api_models::payouts::PayoutRetrieveRequest,
         api_models::payouts::PayoutMethodData,
         api_models::payouts::PayoutLinkResponse,
         api_models::payouts::Bank,
diff --git a/crates/openapi/src/routes/payouts.rs b/crates/openapi/src/routes/payouts.rs
index 63daa85226a..0a429041afe 100644
--- a/crates/openapi/src/routes/payouts.rs
+++ b/crates/openapi/src/routes/payouts.rs
@@ -2,7 +2,7 @@
 #[utoipa::path(
     post,
     path = "/payouts/create",
-    request_body=PayoutCreateRequest,
+    request_body=PayoutsCreateRequest,
     responses(
         (status = 200, description = "Payout created", body = PayoutCreateResponse),
         (status = 400, description = "Missing Mandatory fields")
@@ -18,7 +18,8 @@ pub async fn payouts_create() {}
     get,
     path = "/payouts/{payout_id}",
     params(
-        ("payout_id" = String, Path, description = "The identifier for payout]")
+        ("payout_id" = String, Path, description = "The identifier for payout"),
+        ("force_sync" = Option<bool>, Query, description = "Sync with the connector to get the payout details (defaults to false)")
     ),
     responses(
         (status = 200, description = "Payout retrieved", body = PayoutCreateResponse),
@@ -35,9 +36,9 @@ pub async fn payouts_retrieve() {}
     post,
     path = "/payouts/{payout_id}",
     params(
-        ("payout_id" = String, Path, description = "The identifier for payout]")
+        ("payout_id" = String, Path, description = "The identifier for payout")
     ),
-    request_body=PayoutCreateRequest,
+    request_body=PayoutUpdateRequest,
     responses(
         (status = 200, description = "Payout updated", body = PayoutCreateResponse),
         (status = 400, description = "Missing Mandatory fields")
@@ -55,7 +56,7 @@ pub async fn payouts_update() {}
     params(
         ("payout_id" = String, Path, description = "The identifier for payout")
     ),
-    request_body=PayoutActionRequest,
+    request_body=PayoutCancelRequest,
     responses(
         (status = 200, description = "Payout cancelled", body = PayoutCreateResponse),
         (status = 400, description = "Missing Mandatory fields")
@@ -73,7 +74,7 @@ pub async fn payouts_cancel() {}
     params(
         ("payout_id" = String, Path, description = "The identifier for payout")
     ),
-    request_body=PayoutActionRequest,
+    request_body=PayoutFulfillRequest,
     responses(
         (status = 200, description = "Payout fulfilled", body = PayoutCreateResponse),
         (status = 400, description = "Missing Mandatory fields")
@@ -88,38 +89,61 @@ pub async fn payouts_fulfill() {}
 #[utoipa::path(
     get,
     path = "/payouts/list",
+    params(
+        ("customer_id" = String, Query, description = "The identifier for customer"),
+        ("starting_after" = String, Query, description = "A cursor for use in pagination, fetch the next list after some object"),
+        ("ending_before" = String, Query, description = "A cursor for use in pagination, fetch the previous list before some object"),
+        ("limit" = String, Query, description = "limit on the number of objects to return"),
+        ("created" = String, Query, description = "The time at which payout is created"),
+        ("time_range" = String, Query, description = "The time range for which objects are needed. TimeRange has two fields start_time and end_time from which objects can be filtered as per required scenarios (created_at, time less than, greater than etc).")
+    ),
     responses(
         (status = 200, description = "Payouts listed", body = PayoutListResponse),
         (status = 404, description = "Payout not found")
     ),
     tag = "Payouts",
-    operation_id = "List payouts",
+    operation_id = "List payouts using generic constraints",
     security(("api_key" = []))
 )]
 pub async fn payouts_list() {}
 
-/// Payouts - Filter
+/// Payouts - List available filters
+#[utoipa::path(
+    post,
+    path = "/payouts/filter",
+    request_body=TimeRange,
+    responses(
+        (status = 200, description = "Filters listed", body = PayoutListFilters)
+    ),
+    tag = "Payouts",
+    operation_id = "List available payout filters",
+    security(("api_key" = []))
+)]
+pub async fn payouts_list_filters() {}
+
+/// Payouts - List using filters
 #[utoipa::path(
     post,
     path = "/payouts/list",
+    request_body=PayoutListFilterConstraints,
     responses(
         (status = 200, description = "Payouts filtered", body = PayoutListResponse),
         (status = 404, description = "Payout not found")
     ),
     tag = "Payouts",
-    operation_id = "Filter payouts",
+    operation_id = "Filter payouts using specific constraints",
     security(("api_key" = []))
 )]
-pub async fn payouts_filter() {}
+pub async fn payouts_list_by_filter() {}
 
 /// Payouts - Confirm
 #[utoipa::path(
     post,
     path = "/payouts/{payout_id}/confirm",
     params(
-        ("payout_id" = String, Path, description = "The identifier for payout]")
+        ("payout_id" = String, Path, description = "The identifier for payout")
     ),
-    request_body=PayoutCreateRequest,
+    request_body=PayoutConfirmRequest,
     responses(
         (status = 200, description = "Payout updated", body = PayoutCreateResponse),
         (status = 400, description = "Missing Mandatory fields")
diff --git a/crates/router/src/compatibility/stripe/webhooks.rs b/crates/router/src/compatibility/stripe/webhooks.rs
index 4b08b0d36e8..998702b5de1 100644
--- a/crates/router/src/compatibility/stripe/webhooks.rs
+++ b/crates/router/src/compatibility/stripe/webhooks.rs
@@ -5,10 +5,7 @@ use api_models::{
     webhooks::{self as api},
 };
 #[cfg(feature = "payouts")]
-use common_utils::{
-    crypto::Encryptable,
-    pii::{self, Email},
-};
+use common_utils::pii::{self, Email};
 use common_utils::{crypto::SignMessage, date_time, ext_traits::Encode};
 use error_stack::ResultExt;
 use router_env::logger;
@@ -167,16 +164,25 @@ impl From<common_enums::PayoutStatus> for StripePayoutStatus {
 #[cfg(feature = "payouts")]
 impl From<payout_models::PayoutCreateResponse> for StripePayoutResponse {
     fn from(res: payout_models::PayoutCreateResponse) -> Self {
+        let (name, email, phone, phone_country_code) = match res.customer {
+            Some(customer) => (
+                customer.name,
+                customer.email,
+                customer.phone,
+                customer.phone_country_code,
+            ),
+            None => (None, None, None, None),
+        };
         Self {
             id: res.payout_id,
             amount: res.amount.get_amount_as_i64(),
             currency: res.currency.to_string(),
             payout_type: res.payout_type,
             status: StripePayoutStatus::from(res.status),
-            name: res.name.map(Encryptable::into_inner),
-            email: res.email.map(|inner| inner.into()),
-            phone: res.phone.map(Encryptable::into_inner),
-            phone_country_code: res.phone_country_code,
+            name,
+            email,
+            phone,
+            phone_country_code,
             created: res.created.map(|t| t.assume_utc().unix_timestamp()),
             metadata: res.metadata,
             entity_type: res.entity_type,
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index 298106cfc76..9182bf93606 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -1,16 +1,14 @@
 use std::{fmt::Debug, marker::PhantomData, str::FromStr};
 
 use api_models::payments::{
-    Address, CustomerDetailsResponse, FrmMessage, PaymentChargeRequest, PaymentChargeResponse,
-    RequestSurchargeDetails,
+    Address, CustomerDetails, CustomerDetailsResponse, FrmMessage, PaymentChargeRequest,
+    PaymentChargeResponse, RequestSurchargeDetails,
 };
-#[cfg(feature = "payouts")]
-use api_models::payouts::PayoutAttemptResponse;
 use common_enums::RequestIncrementalAuthorization;
 use common_utils::{consts::X_HS_LATENCY, fp_utils, pii::Email, types::MinorUnit};
 use diesel_models::ephemeral_key;
 use error_stack::{report, ResultExt};
-use hyperswitch_domain_models::payments::payment_intent::CustomerData;
+use hyperswitch_domain_models::{payments::payment_intent::CustomerData, router_request_types};
 use masking::{ExposeInterface, Maskable, PeekInterface, Secret};
 use router_env::{instrument, metrics::add_attributes, tracing};
 
@@ -1094,62 +1092,6 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay
     }
 }
 
-#[cfg(feature = "payouts")]
-impl ForeignFrom<(storage::Payouts, storage::PayoutAttempt, domain::Customer)>
-    for api::PayoutCreateResponse
-{
-    fn foreign_from(item: (storage::Payouts, storage::PayoutAttempt, domain::Customer)) -> Self {
-        let (payout, payout_attempt, customer) = item;
-        let attempt = PayoutAttemptResponse {
-            attempt_id: payout_attempt.payout_attempt_id,
-            status: payout_attempt.status,
-            amount: payout.amount,
-            currency: Some(payout.destination_currency),
-            connector: payout_attempt.connector.clone(),
-            error_code: payout_attempt.error_code.clone(),
-            error_message: payout_attempt.error_message.clone(),
-            payment_method: payout.payout_type,
-            payout_method_type: None,
-            connector_transaction_id: payout_attempt.connector_payout_id,
-            cancellation_reason: None,
-            unified_code: None,
-            unified_message: None,
-        };
-        Self {
-            payout_id: payout.payout_id,
-            merchant_id: payout.merchant_id,
-            amount: payout.amount,
-            currency: payout.destination_currency,
-            connector: payout_attempt.connector,
-            payout_type: payout.payout_type,
-            customer_id: customer.get_customer_id(),
-            auto_fulfill: payout.auto_fulfill,
-            email: customer.email,
-            name: customer.name,
-            phone: customer.phone,
-            phone_country_code: customer.phone_country_code,
-            return_url: payout.return_url,
-            business_country: payout_attempt.business_country,
-            business_label: payout_attempt.business_label,
-            description: payout.description,
-            entity_type: payout.entity_type,
-            recurring: payout.recurring,
-            metadata: payout.metadata,
-            status: payout_attempt.status,
-            error_message: payout_attempt.error_message,
-            error_code: payout_attempt.error_code,
-            profile_id: payout.profile_id,
-            created: Some(payout.created_at),
-            connector_transaction_id: attempt.connector_transaction_id.clone(),
-            priority: payout.priority,
-            attempts: Some(vec![attempt]),
-            billing: None,
-            client_secret: None,
-            payout_link: None,
-        }
-    }
-}
-
 impl ForeignFrom<ephemeral_key::EphemeralKey> for api::ephemeral_key::EphemeralKeyCreateResponse {
     fn foreign_from(from: ephemeral_key::EphemeralKey) -> Self {
         Self {
@@ -1962,3 +1904,15 @@ impl ForeignFrom<payments::FraudCheck> for FrmMessage {
         }
     }
 }
+
+impl ForeignFrom<CustomerDetails> for router_request_types::CustomerDetails {
+    fn foreign_from(customer: CustomerDetails) -> Self {
+        Self {
+            customer_id: Some(customer.id),
+            name: customer.name,
+            email: customer.email,
+            phone: customer.phone,
+            phone_country_code: customer.phone_country_code,
+        }
+    }
+}
diff --git a/crates/router/src/core/payout_link.rs b/crates/router/src/core/payout_link.rs
index e20f373fd1d..da34a82a854 100644
--- a/crates/router/src/core/payout_link.rs
+++ b/crates/router/src/core/payout_link.rs
@@ -6,7 +6,7 @@ use std::{
 use actix_web::http::header;
 use api_models::payouts;
 use common_utils::{
-    ext_traits::{Encode, OptionExt},
+    ext_traits::{AsyncExt, Encode, OptionExt},
     link_utils,
     types::{AmountConvertor, StringMajorUnitForConnector},
 };
@@ -284,14 +284,20 @@ pub async fn filter_payout_methods(
         Some(&payout.profile_id),
         common_enums::ConnectorType::PayoutProcessor,
     );
-    let address = db
-        .find_address_by_address_id(key_manager_state, &payout.address_id.clone(), key_store)
+    let address = payout
+        .address_id
+        .as_ref()
+        .async_map(|address_id| async {
+            db.find_address_by_address_id(key_manager_state, address_id, key_store)
+                .await
+        })
         .await
+        .transpose()
         .change_context(errors::ApiErrorResponse::InternalServerError)
         .attach_printable_lazy(|| {
             format!(
-                "Failed while fetching address with address id {}",
-                payout.address_id.clone()
+                "Failed while fetching address [id - {:?}] for payout [id - {}]",
+                payout.address_id, payout.payout_id
             )
         })?;
 
@@ -326,7 +332,10 @@ pub async fn filter_payout_methods(
                         payout_filter,
                         request_payout_method_type,
                         &payout.destination_currency,
-                        &address.country,
+                        address
+                            .as_ref()
+                            .and_then(|address| address.country)
+                            .as_ref(),
                     )?;
                     if currency_country_filter.unwrap_or(true) {
                         match payment_method {
@@ -381,7 +390,7 @@ pub fn check_currency_country_filters(
     payout_method_filter: Option<&PaymentMethodFilters>,
     request_payout_method_type: &api_models::payment_methods::RequestPaymentMethodTypes,
     currency: &common_enums::Currency,
-    country: &Option<common_enums::CountryAlpha2>,
+    country: Option<&common_enums::CountryAlpha2>,
 ) -> errors::RouterResult<Option<bool>> {
     if matches!(
         request_payout_method_type.payment_method_type,
diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs
index b0819f4a5f8..2debb492ada 100644
--- a/crates/router/src/core/payouts.rs
+++ b/crates/router/src/core/payouts.rs
@@ -2,6 +2,7 @@ pub mod access_token;
 pub mod helpers;
 #[cfg(feature = "payout_retry")]
 pub mod retry;
+pub mod transformers;
 pub mod validator;
 use std::vec::IntoIter;
 
@@ -24,8 +25,6 @@ use diesel_models::{
 use error_stack::{report, ResultExt};
 #[cfg(feature = "olap")]
 use futures::future::join_all;
-#[cfg(feature = "olap")]
-use hyperswitch_domain_models::errors::StorageError;
 use masking::{PeekInterface, Secret};
 #[cfg(feature = "payout_retry")]
 use retry::GsmValidation;
@@ -49,7 +48,7 @@ use crate::{
     services,
     types::{
         self,
-        api::{self, payouts},
+        api::{self, payments as payment_api_types, payouts},
         domain,
         storage::{self, PaymentRoutingInfo},
         transformers::ForeignFrom,
@@ -90,7 +89,7 @@ pub async fn get_connector_choice(
     connector: Option<String>,
     routing_algorithm: Option<serde_json::Value>,
     payout_data: &mut PayoutData,
-    eligible_connectors: Option<Vec<api_models::enums::PayoutConnectors>>,
+    eligible_connectors: Option<Vec<api_enums::PayoutConnectors>>,
 ) -> RouterResult<api::ConnectorCallType> {
     let eligible_routable_connectors = eligible_connectors.map(|connectors| {
         connectors
@@ -255,7 +254,9 @@ pub async fn make_connector_decision(
 
             Ok(())
         }
-        _ => Err(errors::ApiErrorResponse::InternalServerError)?,
+        _ => Err(errors::ApiErrorResponse::InternalServerError).attach_printable({
+            "only PreDetermined and Retryable ConnectorCallTypes are supported".to_string()
+        })?,
     }
 }
 
@@ -266,7 +267,7 @@ pub async fn payouts_core(
     key_store: &domain::MerchantKeyStore,
     payout_data: &mut PayoutData,
     routing_algorithm: Option<serde_json::Value>,
-    eligible_connectors: Option<Vec<api_models::enums::PayoutConnectors>>,
+    eligible_connectors: Option<Vec<api_enums::PayoutConnectors>>,
 ) -> RouterResult<()> {
     let payout_attempt = &payout_data.payout_attempt;
 
@@ -301,7 +302,7 @@ pub async fn payouts_create_core(
     req: payouts::PayoutCreateRequest,
 ) -> RouterResponse<payouts::PayoutCreateResponse> {
     // Validate create request
-    let (payout_id, payout_method_data, profile_id) =
+    let (payout_id, payout_method_data, profile_id, customer) =
         validator::validate_create_request(&state, &merchant_account, &req, &key_store).await?;
 
     // Create DB entries
@@ -313,6 +314,7 @@ pub async fn payouts_create_core(
         &payout_id,
         &profile_id,
         payout_method_data.as_ref(),
+        customer.as_ref(),
     )
     .await?;
 
@@ -320,18 +322,25 @@ pub async fn payouts_create_core(
     let payout_type = payout_data.payouts.payout_type.to_owned();
 
     // Persist payout method data in temp locker
-    payout_data.payout_method_data = helpers::make_payout_method_data(
-        &state,
-        req.payout_method_data.as_ref(),
-        payout_attempt.payout_token.as_deref(),
-        &payout_attempt.customer_id,
-        &payout_attempt.merchant_id,
-        payout_type,
-        &key_store,
-        Some(&mut payout_data),
-        merchant_account.storage_scheme,
-    )
-    .await?;
+    if req.payout_method_data.is_some() {
+        let customer_id = payout_data
+            .payouts
+            .customer_id
+            .clone()
+            .get_required_value("customer_id when payout_method_data is provided")?;
+        payout_data.payout_method_data = helpers::make_payout_method_data(
+            &state,
+            req.payout_method_data.as_ref(),
+            payout_attempt.payout_token.as_deref(),
+            &customer_id,
+            &payout_attempt.merchant_id,
+            payout_type,
+            &key_store,
+            Some(&mut payout_data),
+            merchant_account.storage_scheme,
+        )
+        .await?;
+    }
 
     if let Some(true) = payout_data.payouts.confirm {
         payouts_core(
@@ -380,8 +389,14 @@ pub async fn payouts_confirm_core(
         "confirm",
     )?;
 
-    helpers::update_payouts_and_payout_attempt(&mut payout_data, &merchant_account, &req, &state)
-        .await?;
+    helpers::update_payouts_and_payout_attempt(
+        &mut payout_data,
+        &merchant_account,
+        &req,
+        &state,
+        &key_store,
+    )
+    .await?;
 
     let db = &*state.store;
 
@@ -441,8 +456,14 @@ pub async fn payouts_update_core(
             ),
         }));
     }
-    helpers::update_payouts_and_payout_attempt(&mut payout_data, &merchant_account, &req, &state)
-        .await?;
+    helpers::update_payouts_and_payout_attempt(
+        &mut payout_data,
+        &merchant_account,
+        &req,
+        &state,
+        &key_store,
+    )
+    .await?;
     let payout_attempt = payout_data.payout_attempt.to_owned();
 
     if (req.connector.is_none(), payout_attempt.connector.is_some()) != (true, true) {
@@ -452,18 +473,25 @@ pub async fn payouts_update_core(
     };
 
     // Update payout method data in temp locker
-    payout_data.payout_method_data = helpers::make_payout_method_data(
-        &state,
-        req.payout_method_data.as_ref(),
-        payout_attempt.payout_token.as_deref(),
-        &payout_attempt.customer_id,
-        &payout_attempt.merchant_id,
-        payout_data.payouts.payout_type,
-        &key_store,
-        Some(&mut payout_data),
-        merchant_account.storage_scheme,
-    )
-    .await?;
+    if req.payout_method_data.is_some() {
+        let customer_id = payout_data
+            .payouts
+            .customer_id
+            .clone()
+            .get_required_value("customer_id when payout_method_data is provided")?;
+        payout_data.payout_method_data = helpers::make_payout_method_data(
+            &state,
+            req.payout_method_data.as_ref(),
+            payout_attempt.payout_token.as_deref(),
+            &customer_id,
+            &payout_attempt.merchant_id,
+            payout_data.payouts.payout_type,
+            &key_store,
+            Some(&mut payout_data),
+            merchant_account.storage_scheme,
+        )
+        .await?;
+    }
 
     if let Some(true) = payout_data.payouts.confirm {
         payouts_core(
@@ -672,12 +700,17 @@ pub async fn payouts_fulfill_core(
     };
 
     // Trigger fulfillment
+    let customer_id = payout_data
+        .payouts
+        .customer_id
+        .clone()
+        .get_required_value("customer_id")?;
     payout_data.payout_method_data = Some(
         helpers::make_payout_method_data(
             &state,
             None,
             payout_attempt.payout_token.as_deref(),
-            &payout_attempt.customer_id,
+            &customer_id,
             &payout_attempt.merchant_id,
             payout_data.payouts.payout_type,
             &key_store,
@@ -729,70 +762,77 @@ pub async fn payouts_list_core(
     .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;
     let payouts = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, payouts);
 
-    let collected_futures = payouts.into_iter().map(|payouts| async {
+    let collected_futures = payouts.into_iter().map(|payout| async {
         match db
             .find_payout_attempt_by_merchant_id_payout_attempt_id(
                 merchant_id,
-                &utils::get_payment_attempt_id(payouts.payout_id.clone(), payouts.attempt_count),
+                &utils::get_payment_attempt_id(payout.payout_id.clone(), payout.attempt_count),
                 storage_enums::MerchantStorageScheme::PostgresOnly,
             )
             .await
         {
-            Ok(payout_attempt) => {
-                match db
-                    .find_customer_by_customer_id_merchant_id(
-                        &(&state).into(),
-                        &payouts.customer_id,
-                        merchant_id,
-                        &key_store,
-                        merchant_account.storage_scheme,
-                    )
-                    .await
-                {
-                    Ok(customer) => Some(Ok((payouts, payout_attempt, customer))),
-                    Err(error) => {
-                        if matches!(
-                            error.current_context(),
-                            storage_impl::errors::StorageError::ValueNotFound(_)
-                        ) {
-                            logger::warn!(
-                                ?error,
-                                "customer missing for customer_id : {:?}",
-                                payouts.customer_id,
+            Ok(ref payout_attempt) => match payout.customer_id.clone() {
+                Some(ref customer_id) => {
+                    match db
+                        .find_customer_by_customer_id_merchant_id(
+                            &(&state).into(),
+                            customer_id,
+                            merchant_id,
+                            &key_store,
+                            merchant_account.storage_scheme,
+                        )
+                        .await
+                    {
+                        Ok(customer) => Ok((payout, payout_attempt.to_owned(), Some(customer))),
+                        Err(err) => {
+                            let err_msg = format!(
+                                "failed while fetching customer for customer_id - {:?}",
+                                customer_id
                             );
-                            return None;
+                            logger::warn!(?err, err_msg);
+                            if err.current_context().is_db_not_found() {
+                                Ok((payout, payout_attempt.to_owned(), None))
+                            } else {
+                                Err(err
+                                    .change_context(errors::ApiErrorResponse::InternalServerError)
+                                    .attach_printable(err_msg))
+                            }
                         }
-                        Some(Err(error.change_context(StorageError::ValueNotFound(
-                            format!(
-                                "customer missing for customer_id : {:?}",
-                                payouts.customer_id
-                            ),
-                        ))))
                     }
                 }
-            }
-            Err(error) => {
-                if matches!(error.current_context(), StorageError::ValueNotFound(_)) {
-                    logger::warn!(
-                        ?error,
-                        "payout_attempt missing for payout_id : {}",
-                        payouts.payout_id,
-                    );
-                    return None;
-                }
-                Some(Err(error))
+                None => Ok((payout.to_owned(), payout_attempt.to_owned(), None)),
+            },
+            Err(err) => {
+                let err_msg = format!(
+                    "failed while fetching payout_attempt for payout_id - {}",
+                    payout.payout_id.clone(),
+                );
+                logger::warn!(?err, err_msg);
+                Err(err
+                    .change_context(errors::ApiErrorResponse::InternalServerError)
+                    .attach_printable(err_msg))
             }
         }
     });
 
     let pi_pa_tuple_vec: Result<
-        Vec<(storage::Payouts, storage::PayoutAttempt, domain::Customer)>,
+        Vec<(
+            storage::Payouts,
+            storage::PayoutAttempt,
+            Option<domain::Customer>,
+        )>,
         _,
     > = join_all(collected_futures)
         .await
         .into_iter()
-        .flatten()
-        .collect::<Result<Vec<(storage::Payouts, storage::PayoutAttempt, domain::Customer)>, _>>();
+        .collect::<Result<
+            Vec<(
+                storage::Payouts,
+                storage::PayoutAttempt,
+                Option<domain::Customer>,
+            )>,
+            _,
+        >>();
 
     let data: Vec<api::PayoutCreateResponse> = pi_pa_tuple_vec
         .change_context(errors::ApiErrorResponse::InternalServerError)?
@@ -822,7 +862,7 @@ pub async fn payouts_filtered_list_core(
     let list: Vec<(
         storage::Payouts,
         storage::PayoutAttempt,
-        diesel_models::Customer,
+        Option<diesel_models::Customer>,
     )> = db
         .filter_payouts_and_attempts(
             merchant_account.get_id(),
@@ -833,24 +873,23 @@ pub async fn payouts_filtered_list_core(
         .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;
     let list = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, list);
     let data: Vec<api::PayoutCreateResponse> = join_all(list.into_iter().map(|(p, pa, c)| async {
-        match domain::Customer::convert_back(
-            &(&state).into(),
-            c,
-            &key_store.key,
-            key_store.merchant_id.clone().into(),
-        )
-        .await
-        {
-            Ok(domain_cust) => Some((p, pa, domain_cust)),
-            Err(err) => {
-                logger::warn!(
-                    ?err,
-                    "failed to convert customer for id: {:?}",
-                    p.customer_id
-                );
-                None
-            }
-        }
+        let domain_cust = c
+            .async_and_then(|cust| async {
+                domain::Customer::convert_back(
+                    &(&state).into(),
+                    cust,
+                    &key_store.key,
+                    key_store.merchant_id.clone().into(),
+                )
+                .await
+                .map_err(|err| {
+                    let msg = format!("failed to convert customer for id: {:?}", p.customer_id);
+                    logger::warn!(?err, msg);
+                })
+                .ok()
+            })
+            .await;
+        Some((p, pa, domain_cust))
     }))
     .await
     .into_iter()
@@ -936,12 +975,16 @@ pub async fn call_connector_payout(
 
     // Fetch / store payout_method_data
     if payout_data.payout_method_data.is_none() || payout_attempt.payout_token.is_none() {
+        let customer_id = payouts
+            .customer_id
+            .clone()
+            .get_required_value("customer_id")?;
         payout_data.payout_method_data = Some(
             helpers::make_payout_method_data(
                 state,
                 payout_data.payout_method_data.to_owned().as_ref(),
                 payout_attempt.payout_token.as_deref(),
-                &payout_attempt.customer_id,
+                &customer_id,
                 &payout_attempt.merchant_id,
                 payouts.payout_type,
                 key_store,
@@ -1975,22 +2018,6 @@ pub async fn fulfill_payout(
                 .status
                 .unwrap_or(payout_data.payout_attempt.status.to_owned());
             payout_data.payouts.status = status;
-            if payout_data.payouts.recurring
-                && payout_data.payouts.payout_method_id.clone().is_none()
-                && !helpers::is_payout_err_state(status)
-            {
-                helpers::save_payout_data_to_locker(
-                    state,
-                    payout_data,
-                    &payout_data
-                        .payout_method_data
-                        .clone()
-                        .get_required_value("payout_method_data")?,
-                    merchant_account,
-                    key_store,
-                )
-                .await?;
-            }
             let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                 connector_payout_id: payout_response_data.connector_payout_id,
                 status,
@@ -2024,6 +2051,31 @@ pub async fn fulfill_payout(
                         serde_json::json!({"payout_status": status.to_string(), "error_message": payout_data.payout_attempt.error_message.as_ref(), "error_code": payout_data.payout_attempt.error_code.as_ref()})
                     ),
                 }));
+            } else if payout_data.payouts.recurring
+                && payout_data.payouts.payout_method_id.clone().is_none()
+            {
+                let payout_method_data = payout_data
+                    .payout_method_data
+                    .clone()
+                    .get_required_value("payout_method_data")?;
+                payout_data
+                    .payouts
+                    .customer_id
+                    .clone()
+                    .async_map(|customer_id| async move {
+                        helpers::save_payout_data_to_locker(
+                            state,
+                            payout_data,
+                            &customer_id,
+                            &payout_method_data,
+                            merchant_account,
+                            key_store,
+                        )
+                        .await
+                    })
+                    .await
+                    .transpose()
+                    .attach_printable("Failed to save payout data to locker")?;
             }
         }
         Err(err) => {
@@ -2072,17 +2124,12 @@ pub async fn response_handler(
     let customer_details = payout_data.customer_details.to_owned();
     let customer_id = payouts.customer_id;
 
-    let (email, name, phone, phone_country_code) = customer_details
-        .map_or((None, None, None, None), |c| {
-            (c.email, c.name, c.phone, c.phone_country_code)
-        });
-
     let address = billing_address.as_ref().map(|a| {
-        let phone_details = api_models::payments::PhoneDetails {
+        let phone_details = payment_api_types::PhoneDetails {
             number: a.phone_number.to_owned().map(Encryptable::into_inner),
             country_code: a.country_code.to_owned(),
         };
-        let address_details = api_models::payments::AddressDetails {
+        let address_details = payment_api_types::AddressDetails {
             city: a.city.to_owned(),
             country: a.country.to_owned(),
             line1: a.line1.to_owned().map(Encryptable::into_inner),
@@ -2108,12 +2155,17 @@ pub async fn response_handler(
         connector: payout_attempt.connector.to_owned(),
         payout_type: payouts.payout_type.to_owned(),
         billing: address,
-        customer_id,
         auto_fulfill: payouts.auto_fulfill,
-        email,
-        name,
-        phone,
-        phone_country_code,
+        customer_id,
+        email: customer_details.as_ref().and_then(|c| c.email.clone()),
+        name: customer_details.as_ref().and_then(|c| c.name.clone()),
+        phone: customer_details.as_ref().and_then(|c| c.phone.clone()),
+        phone_country_code: customer_details
+            .as_ref()
+            .and_then(|c| c.phone_country_code.clone()),
+        customer: customer_details
+            .as_ref()
+            .map(payment_api_types::CustomerDetailsResponse::foreign_from),
         client_secret: payouts.client_secret.to_owned(),
         return_url: payouts.return_url.to_owned(),
         business_country: payout_attempt.business_country,
@@ -2154,33 +2206,11 @@ pub async fn payout_create_db_entries(
     payout_id: &String,
     profile_id: &String,
     stored_payout_method_data: Option<&payouts::PayoutMethodData>,
+    customer: Option<&domain::Customer>,
 ) -> RouterResult<PayoutData> {
     let db = &*state.store;
     let merchant_id = merchant_account.get_id();
-
-    // Get or create customer
-    let customer_details = payments::CustomerDetails {
-        customer_id: req.customer_id.to_owned(),
-        name: req.name.to_owned(),
-        email: req.email.to_owned(),
-        phone: req.phone.to_owned(),
-        phone_country_code: req.phone_country_code.to_owned(),
-    };
-    let customer = helpers::get_or_create_customer_details(
-        state,
-        &customer_details,
-        merchant_account,
-        key_store,
-    )
-    .await?;
-    let customer_id = customer
-        .to_owned()
-        .ok_or_else(|| {
-            report!(errors::ApiErrorResponse::MissingRequiredField {
-                field_name: "customer_id",
-            })
-        })?
-        .get_customer_id();
+    let customer_id = customer.map(|cust| cust.get_customer_id());
 
     // Validate whether profile_id passed in request is valid and is linked to the merchant
     let business_profile =
@@ -2191,8 +2221,10 @@ pub async fn payout_create_db_entries(
             create_payout_link(
                 state,
                 &business_profile,
-                &customer_id,
-                merchant_account.get_id(),
+                &customer_id
+                    .clone()
+                    .get_required_value("customer.id when payout_link is true")?,
+                merchant_id,
                 req,
                 payout_id,
             )
@@ -2207,20 +2239,13 @@ pub async fn payout_create_db_entries(
         req.billing.as_ref(),
         None,
         merchant_id,
-        Some(&customer_id.to_owned()),
+        customer_id.as_ref(),
         key_store,
         payout_id,
         merchant_account.storage_scheme,
     )
     .await?;
-    let address_id = billing_address
-        .to_owned()
-        .ok_or_else(|| {
-            report!(errors::ApiErrorResponse::MissingRequiredField {
-                field_name: "billing.address",
-            })
-        })?
-        .address_id;
+    let address_id = billing_address.to_owned().map(|address| address.address_id);
 
     // Make payouts entry
     let currency = req.currency.to_owned().get_required_value("currency")?;
@@ -2251,7 +2276,7 @@ pub async fn payout_create_db_entries(
     let payouts_req = storage::PayoutsNew {
         payout_id: payout_id.to_string(),
         merchant_id: merchant_id.to_owned(),
-        customer_id: customer_id.to_owned(),
+        customer_id,
         address_id: address_id.to_owned(),
         payout_type,
         amount,
@@ -2288,9 +2313,7 @@ pub async fn payout_create_db_entries(
     let payout_attempt_req = storage::PayoutAttemptNew {
         payout_attempt_id: payout_attempt_id.to_string(),
         payout_id: payout_id.to_owned(),
-        customer_id: customer_id.to_owned(),
         merchant_id: merchant_id.to_owned(),
-        address_id: address_id.to_owned(),
         status,
         business_country: req.business_country.to_owned(),
         business_label: req.business_label.to_owned(),
@@ -2314,7 +2337,7 @@ pub async fn payout_create_db_entries(
     Ok(PayoutData {
         billing_address,
         business_profile,
-        customer_details: customer,
+        customer_details: customer.map(ToOwned::to_owned),
         merchant_connector_account: None,
         payouts,
         payout_attempt,
@@ -2365,28 +2388,41 @@ pub async fn make_payout_data(
         .await
         .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;
 
+    let customer_id = payouts.customer_id.as_ref();
+    let payout_id = &payouts.payout_id;
     let billing_address = payment_helpers::create_or_find_address_for_payment_by_request(
         state,
         None,
-        Some(&payouts.address_id.to_owned()),
+        payouts.address_id.as_deref(),
         merchant_id,
-        Some(&payouts.customer_id.to_owned()),
+        customer_id,
         key_store,
-        &payouts.payout_id,
+        payout_id,
         merchant_account.storage_scheme,
     )
     .await?;
 
-    let customer_details = db
-        .find_customer_optional_by_customer_id_merchant_id(
-            &state.into(),
-            &payouts.customer_id.to_owned(),
-            merchant_id,
-            key_store,
-            merchant_account.storage_scheme,
-        )
+    let customer_details = customer_id
+        .async_map(|customer_id| async move {
+            db.find_customer_optional_by_customer_id_merchant_id(
+                &state.into(),
+                customer_id,
+                merchant_id,
+                key_store,
+                merchant_account.storage_scheme,
+            )
+            .await
+            .map_err(|err| err.change_context(errors::ApiErrorResponse::InternalServerError))
+            .attach_printable_lazy(|| {
+                format!(
+                    "Failed while fetching optional customer [id - {:?}] for payout [id - {}]",
+                    customer_id, payout_id
+                )
+            })
+        })
         .await
-        .map_or(None, |c| c);
+        .transpose()?
+        .and_then(|c| c);
 
     let profile_id = payout_attempt.profile_id.clone();
 
@@ -2401,7 +2437,7 @@ pub async fn make_payout_data(
                     let customer_id = customer_details
                         .as_ref()
                         .map(|cd| cd.get_customer_id().to_owned())
-                        .get_required_value("customer")?;
+                        .get_required_value("customer_id when payout_token is sent")?;
                     helpers::make_payout_method_data(
                         state,
                         None,
diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs
index b17ea437560..4714992799a 100644
--- a/crates/router/src/core/payouts/helpers.rs
+++ b/crates/router/src/core/payouts/helpers.rs
@@ -20,6 +20,7 @@ use router_env::logger;
 
 use super::PayoutData;
 use crate::{
+    consts,
     core::{
         errors::{self, RouterResult, StorageErrorExt},
         payment_methods::{
@@ -28,8 +29,8 @@ use crate::{
             vault,
         },
         payments::{
-            customers::get_connector_customer_details_if_present, route_connector_v1, routing,
-            CustomerDetails,
+            customers::get_connector_customer_details_if_present, helpers as payment_helpers,
+            route_connector_v1, routing, CustomerDetails,
         },
         routing::TransactionData,
     },
@@ -194,11 +195,12 @@ pub async fn make_payout_method_data<'a>(
 pub async fn save_payout_data_to_locker(
     state: &SessionState,
     payout_data: &mut PayoutData,
+    customer_id: &id_type::CustomerId,
     payout_method_data: &api::PayoutMethodData,
     merchant_account: &domain::MerchantAccount,
     key_store: &domain::MerchantKeyStore,
 ) -> RouterResult<()> {
-    let payout_attempt = &payout_data.payout_attempt;
+    let payouts = &payout_data.payouts;
     let (mut locker_req, card_details, bank_details, wallet_details, payment_method_type) =
         match payout_method_data {
             payouts::PayoutMethodData::Card(card) => {
@@ -215,7 +217,7 @@ pub async fn save_payout_data_to_locker(
                 };
                 let payload = StoreLockerReq::LockerCard(StoreCardReq {
                     merchant_id: merchant_account.get_id().clone(),
-                    merchant_customer_id: payout_attempt.customer_id.to_owned(),
+                    merchant_customer_id: customer_id.to_owned(),
                     card: Card {
                         card_number: card.card_number.to_owned(),
                         name_on_card: card.card_holder_name.to_owned(),
@@ -271,7 +273,7 @@ pub async fn save_payout_data_to_locker(
                 })?;
                 let payload = StoreLockerReq::LockerGeneric(StoreGenericReq {
                     merchant_id: merchant_account.get_id().to_owned(),
-                    merchant_customer_id: payout_attempt.customer_id.to_owned(),
+                    merchant_customer_id: customer_id.to_owned(),
                     enc_data,
                     ttl: state.conf.locker.ttl_for_storage_in_secs,
                 });
@@ -301,7 +303,7 @@ pub async fn save_payout_data_to_locker(
     let stored_resp = cards::call_to_locker_hs(
         state,
         &locker_req,
-        &payout_attempt.customer_id,
+        customer_id,
         api_enums::LockerChoice::HyperswitchCardVault,
     )
     .await
@@ -403,7 +405,7 @@ pub async fn save_payout_data_to_locker(
                 card: card_details.clone(),
                 wallet: None,
                 metadata: None,
-                customer_id: Some(payout_attempt.customer_id.to_owned()),
+                customer_id: Some(customer_id.to_owned()),
                 card_network: None,
                 client_secret: None,
                 payment_method_data: None,
@@ -490,7 +492,7 @@ pub async fn save_payout_data_to_locker(
                     card: None,
                     wallet: wallet_details,
                     metadata: None,
-                    customer_id: Some(payout_attempt.customer_id.to_owned()),
+                    customer_id: Some(customer_id.to_owned()),
                     card_network: None,
                     client_secret: None,
                     payment_method_data: None,
@@ -503,11 +505,11 @@ pub async fn save_payout_data_to_locker(
 
     // Insert new entry in payment_methods table
     if should_insert_in_pm_table {
-        let payment_method_id = common_utils::generate_id(crate::consts::ID_LENGTH, "pm");
+        let payment_method_id = common_utils::generate_id(consts::ID_LENGTH, "pm");
         cards::create_payment_method(
             state,
             &new_payment_method,
-            &payout_attempt.customer_id,
+            customer_id,
             &payment_method_id,
             Some(stored_resp.card_reference.clone()),
             merchant_account.get_id(),
@@ -538,7 +540,7 @@ pub async fn save_payout_data_to_locker(
         // Delete from locker
         cards::delete_card_from_hs_locker(
             state,
-            &payout_attempt.customer_id,
+            customer_id,
             merchant_account.get_id(),
             card_reference,
         )
@@ -553,7 +555,7 @@ pub async fn save_payout_data_to_locker(
         let stored_resp = cards::call_to_locker_hs(
             state,
             &locker_req,
-            &payout_attempt.customer_id,
+            customer_id,
             api_enums::LockerChoice::HyperswitchCardVault,
         )
         .await
@@ -590,9 +592,9 @@ pub async fn save_payout_data_to_locker(
     };
     payout_data.payouts = db
         .update_payout(
-            &payout_data.payouts,
+            payouts,
             updated_payout,
-            payout_attempt,
+            &payout_data.payout_attempt,
             merchant_account.storage_scheme,
         )
         .await
@@ -603,7 +605,7 @@ pub async fn save_payout_data_to_locker(
 }
 
 #[cfg(all(feature = "v2", feature = "customer_v2"))]
-pub async fn get_or_create_customer_details(
+pub(super) async fn get_or_create_customer_details(
     _state: &SessionState,
     _customer_details: &CustomerDetails,
     _merchant_account: &domain::MerchantAccount,
@@ -613,7 +615,7 @@ pub async fn get_or_create_customer_details(
 }
 
 #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
-pub async fn get_or_create_customer_details(
+pub(super) async fn get_or_create_customer_details(
     state: &SessionState,
     customer_details: &CustomerDetails,
     merchant_account: &domain::MerchantAccount,
@@ -641,56 +643,78 @@ pub async fn get_or_create_customer_details(
         .await
         .change_context(errors::ApiErrorResponse::InternalServerError)?
     {
+        // Customer found
         Some(customer) => Ok(Some(customer)),
-        None => {
-            let encrypted_data = crypto_operation(
-                &state.into(),
-                type_name!(domain::Customer),
-                CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable(
-                    CustomerRequestWithEmail {
-                        name: customer_details.name.clone(),
-                        email: customer_details.email.clone(),
-                        phone: customer_details.phone.clone(),
-                    },
-                )),
-                Identifier::Merchant(key_store.merchant_id.clone()),
-                key,
-            )
-            .await
-            .and_then(|val| val.try_into_batchoperation())
-            .change_context(errors::ApiErrorResponse::InternalServerError)?;
-            let encryptable_customer =
-                CustomerRequestWithEmail::from_encryptable(encrypted_data)
-                    .change_context(errors::ApiErrorResponse::InternalServerError)?;
-
-            let customer = domain::Customer {
-                customer_id,
-                merchant_id: merchant_id.to_owned().clone(),
-                name: encryptable_customer.name,
-                email: encryptable_customer.email,
-                phone: encryptable_customer.phone,
-                description: None,
-                phone_country_code: customer_details.phone_country_code.to_owned(),
-                metadata: None,
-                connector_customer: None,
-                created_at: common_utils::date_time::now(),
-                modified_at: common_utils::date_time::now(),
-                address_id: None,
-                default_payment_method_id: None,
-                updated_by: None,
-                version: common_enums::ApiVersion::V1,
-            };
 
-            Ok(Some(
-                db.insert_customer(
-                    customer,
-                    key_manager_state,
-                    key_store,
-                    merchant_account.storage_scheme,
+        // Customer not found
+        // create only if atleast one of the fields were provided for customer creation or else throw error
+        None => {
+            if customer_details.name.is_some()
+                || customer_details.email.is_some()
+                || customer_details.phone.is_some()
+                || customer_details.phone_country_code.is_some()
+            {
+                let encrypted_data = crypto_operation(
+                    &state.into(),
+                    type_name!(domain::Customer),
+                    CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable(
+                        CustomerRequestWithEmail {
+                            name: customer_details.name.clone(),
+                            email: customer_details.email.clone(),
+                            phone: customer_details.phone.clone(),
+                        },
+                    )),
+                    Identifier::Merchant(key_store.merchant_id.clone()),
+                    key,
                 )
                 .await
-                .change_context(errors::ApiErrorResponse::InternalServerError)?,
-            ))
+                .and_then(|val| val.try_into_batchoperation())
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Failed to encrypt customer")?;
+                let encryptable_customer =
+                    CustomerRequestWithEmail::from_encryptable(encrypted_data)
+                        .change_context(errors::ApiErrorResponse::InternalServerError)
+                        .attach_printable("Failed to form EncryptableCustomer")?;
+
+                let customer = domain::Customer {
+                    customer_id: customer_id.clone(),
+                    merchant_id: merchant_id.to_owned().clone(),
+                    name: encryptable_customer.name,
+                    email: encryptable_customer.email,
+                    phone: encryptable_customer.phone,
+                    description: None,
+                    phone_country_code: customer_details.phone_country_code.to_owned(),
+                    metadata: None,
+                    connector_customer: None,
+                    created_at: common_utils::date_time::now(),
+                    modified_at: common_utils::date_time::now(),
+                    address_id: None,
+                    default_payment_method_id: None,
+                    updated_by: None,
+                    version: consts::API_VERSION,
+                };
+
+                Ok(Some(
+                    db.insert_customer(
+                        customer,
+                        key_manager_state,
+                        key_store,
+                        merchant_account.storage_scheme,
+                    )
+                    .await
+                    .change_context(errors::ApiErrorResponse::InternalServerError)
+                    .attach_printable_lazy(|| {
+                        format!(
+                            "Failed to insert customer [id - {:?}] for merchant [id - {:?}]",
+                            customer_id, merchant_id
+                        )
+                    })?,
+                ))
+            } else {
+                Err(report!(errors::ApiErrorResponse::InvalidRequestData {
+                    message: format!("customer for id - {:?} not found", customer_id),
+                }))
+            }
         }
     }
 }
@@ -997,6 +1021,7 @@ pub async fn update_payouts_and_payout_attempt(
     merchant_account: &domain::MerchantAccount,
     req: &payouts::PayoutCreateRequest,
     state: &SessionState,
+    merchant_key_store: &domain::MerchantKeyStore,
 ) -> CustomResult<(), errors::ApiErrorResponse> {
     let payout_attempt = payout_data.payout_attempt.to_owned();
     let status = payout_attempt.status;
@@ -1011,6 +1036,47 @@ pub async fn update_payouts_and_payout_attempt(
         }));
     }
 
+    // Fetch customer details from request and create new or else use existing customer that was attached
+    let customer = get_customer_details_from_request(req);
+    let customer_id = if customer.customer_id.is_some()
+        || customer.name.is_some()
+        || customer.email.is_some()
+        || customer.phone.is_some()
+        || customer.phone_country_code.is_some()
+    {
+        payout_data.customer_details =
+            get_or_create_customer_details(state, &customer, merchant_account, merchant_key_store)
+                .await?;
+        payout_data
+            .customer_details
+            .as_ref()
+            .map(|customer| customer.get_customer_id())
+    } else {
+        payout_data.payouts.customer_id.clone()
+    };
+
+    // Fetch address details from request and create new or else use existing address that was attached
+    let billing_address = payment_helpers::create_or_find_address_for_payment_by_request(
+        state,
+        req.billing.as_ref(),
+        None,
+        merchant_account.get_id(),
+        customer_id.as_ref(),
+        merchant_key_store,
+        &payout_id,
+        merchant_account.storage_scheme,
+    )
+    .await?;
+    let address_id = if billing_address.is_some() {
+        payout_data.billing_address = billing_address;
+        payout_data
+            .billing_address
+            .as_ref()
+            .map(|address| address.address_id.clone())
+    } else {
+        payout_data.payouts.address_id.clone()
+    };
+
     // Update DB with new data
     let payouts = payout_data.payouts.to_owned();
     let amount = MinorUnit::from(req.amount.unwrap_or(payouts.amount.into()));
@@ -1042,6 +1108,8 @@ pub async fn update_payouts_and_payout_attempt(
             .payout_type
             .to_owned()
             .or(payouts.payout_type.to_owned()),
+        address_id: address_id.clone(),
+        customer_id: customer_id.clone(),
     };
     let db = &*state.store;
     payout_data.payouts = db
@@ -1070,25 +1138,66 @@ pub async fn update_payouts_and_payout_attempt(
                     .to_owned()
                     .and_then(|nl| if nl != l { Some(nl) } else { None })
             });
-    match (updated_business_country, updated_business_label) {
-        (None, None) => Ok(()),
-        (business_country, business_label) => {
-            let payout_attempt = &payout_data.payout_attempt;
-            let updated_payout_attempt = storage::PayoutAttemptUpdate::BusinessUpdate {
-                business_country,
-                business_label,
-            };
-            payout_data.payout_attempt = db
-                .update_payout_attempt(
-                    payout_attempt,
-                    updated_payout_attempt,
-                    &payout_data.payouts,
-                    merchant_account.storage_scheme,
-                )
-                .await
-                .change_context(errors::ApiErrorResponse::InternalServerError)
-                .attach_printable("Error updating payout_attempt")?;
-            Ok(())
-        }
+    if updated_business_country.is_some()
+        || updated_business_label.is_some()
+        || customer_id.is_some()
+        || address_id.is_some()
+    {
+        let payout_attempt = &payout_data.payout_attempt;
+        let updated_payout_attempt = storage::PayoutAttemptUpdate::BusinessUpdate {
+            business_country: updated_business_country,
+            business_label: updated_business_label,
+            address_id,
+            customer_id,
+        };
+        payout_data.payout_attempt = db
+            .update_payout_attempt(
+                payout_attempt,
+                updated_payout_attempt,
+                &payout_data.payouts,
+                merchant_account.storage_scheme,
+            )
+            .await
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("Error updating payout_attempt")?;
+    }
+    Ok(())
+}
+
+pub(super) fn get_customer_details_from_request(
+    request: &payouts::PayoutCreateRequest,
+) -> CustomerDetails {
+    let customer_id = request.get_customer_id().map(ToOwned::to_owned);
+
+    let customer_name = request
+        .customer
+        .as_ref()
+        .and_then(|customer_details| customer_details.name.clone())
+        .or(request.name.clone());
+
+    let customer_email = request
+        .customer
+        .as_ref()
+        .and_then(|customer_details| customer_details.email.clone())
+        .or(request.email.clone());
+
+    let customer_phone = request
+        .customer
+        .as_ref()
+        .and_then(|customer_details| customer_details.phone.clone())
+        .or(request.phone.clone());
+
+    let customer_phone_code = request
+        .customer
+        .as_ref()
+        .and_then(|customer_details| customer_details.phone_country_code.clone())
+        .or(request.phone_country_code.clone());
+
+    CustomerDetails {
+        customer_id,
+        name: customer_name,
+        email: customer_email,
+        phone: customer_phone,
+        phone_country_code: customer_phone_code,
     }
 }
diff --git a/crates/router/src/core/payouts/transformers.rs b/crates/router/src/core/payouts/transformers.rs
new file mode 100644
index 00000000000..d8df7271b71
--- /dev/null
+++ b/crates/router/src/core/payouts/transformers.rs
@@ -0,0 +1,76 @@
+use crate::types::{
+    api, domain, storage,
+    transformers::{ForeignFrom, ForeignInto},
+};
+
+impl
+    ForeignFrom<(
+        storage::Payouts,
+        storage::PayoutAttempt,
+        Option<domain::Customer>,
+    )> for api::PayoutCreateResponse
+{
+    fn foreign_from(
+        item: (
+            storage::Payouts,
+            storage::PayoutAttempt,
+            Option<domain::Customer>,
+        ),
+    ) -> Self {
+        let (payout, payout_attempt, customer) = item;
+        let attempt = api::PayoutAttemptResponse {
+            attempt_id: payout_attempt.payout_attempt_id,
+            status: payout_attempt.status,
+            amount: payout.amount,
+            currency: Some(payout.destination_currency),
+            connector: payout_attempt.connector.clone(),
+            error_code: payout_attempt.error_code.clone(),
+            error_message: payout_attempt.error_message.clone(),
+            payment_method: payout.payout_type,
+            payout_method_type: None,
+            connector_transaction_id: payout_attempt.connector_payout_id,
+            cancellation_reason: None,
+            unified_code: None,
+            unified_message: None,
+        };
+        Self {
+            payout_id: payout.payout_id,
+            merchant_id: payout.merchant_id,
+            amount: payout.amount,
+            currency: payout.destination_currency,
+            connector: payout_attempt.connector,
+            payout_type: payout.payout_type,
+            auto_fulfill: payout.auto_fulfill,
+            customer_id: customer.as_ref().map(|cust| cust.get_customer_id()),
+            customer: customer.as_ref().map(|cust| cust.foreign_into()),
+            return_url: payout.return_url,
+            business_country: payout_attempt.business_country,
+            business_label: payout_attempt.business_label,
+            description: payout.description,
+            entity_type: payout.entity_type,
+            recurring: payout.recurring,
+            metadata: payout.metadata,
+            status: payout_attempt.status,
+            error_message: payout_attempt.error_message,
+            error_code: payout_attempt.error_code,
+            profile_id: payout.profile_id,
+            created: Some(payout.created_at),
+            connector_transaction_id: attempt.connector_transaction_id.clone(),
+            priority: payout.priority,
+            attempts: Some(vec![attempt]),
+            billing: None,
+            client_secret: None,
+            payout_link: None,
+            email: customer
+                .as_ref()
+                .and_then(|customer| customer.email.clone()),
+            name: customer.as_ref().and_then(|customer| customer.name.clone()),
+            phone: customer
+                .as_ref()
+                .and_then(|customer| customer.phone.clone()),
+            phone_country_code: customer
+                .as_ref()
+                .and_then(|customer| customer.phone_country_code.clone()),
+        }
+    }
+}
diff --git a/crates/router/src/core/payouts/validator.rs b/crates/router/src/core/payouts/validator.rs
index d8cb20aac05..c73c3907d51 100644
--- a/crates/router/src/core/payouts/validator.rs
+++ b/crates/router/src/core/payouts/validator.rs
@@ -53,12 +53,17 @@ pub async fn validate_create_request(
     merchant_account: &domain::MerchantAccount,
     req: &payouts::PayoutCreateRequest,
     merchant_key_store: &domain::MerchantKeyStore,
-) -> RouterResult<(String, Option<payouts::PayoutMethodData>, String)> {
+) -> RouterResult<(
+    String,
+    Option<payouts::PayoutMethodData>,
+    String,
+    Option<domain::Customer>,
+)> {
     let merchant_id = merchant_account.get_id();
 
     if let Some(payout_link) = &req.payout_link {
         if *payout_link {
-            validate_payout_link_request(req.confirm)?;
+            validate_payout_link_request(req)?;
         }
     };
 
@@ -96,28 +101,46 @@ pub async fn validate_create_request(
         None => Ok(()),
     }?;
 
-    // Payout token
-    let payout_method_data = match req.payout_token.to_owned() {
-        Some(payout_token) => {
-            let customer_id = req
-                .customer_id
-                .to_owned()
-                .unwrap_or_else(common_utils::generate_customer_id_of_default_length);
+    // Fetch customer details (merge of loose fields + customer object) and create DB entry
+    let customer_in_request = helpers::get_customer_details_from_request(req);
+    let customer = if customer_in_request.customer_id.is_some()
+        || customer_in_request.name.is_some()
+        || customer_in_request.email.is_some()
+        || customer_in_request.phone.is_some()
+        || customer_in_request.phone_country_code.is_some()
+    {
+        helpers::get_or_create_customer_details(
+            state,
+            &customer_in_request,
+            merchant_account,
+            merchant_key_store,
+        )
+        .await?
+    } else {
+        None
+    };
+
+    // payout_token
+    let payout_method_data = match (req.payout_token.as_ref(), customer.as_ref()) {
+        (Some(_), None) => Err(report!(errors::ApiErrorResponse::MissingRequiredField {
+            field_name: "customer or customer_id when payout_token is provided"
+        })),
+        (Some(payout_token), Some(customer)) => {
             helpers::make_payout_method_data(
                 state,
                 req.payout_method_data.as_ref(),
-                Some(&payout_token),
-                &customer_id,
+                Some(payout_token),
+                &customer.customer_id,
                 merchant_account.get_id(),
                 req.payout_type,
                 merchant_key_store,
                 None,
                 merchant_account.storage_scheme,
             )
-            .await?
+            .await
         }
-        None => None,
-    };
+        _ => Ok(None),
+    }?;
 
     #[cfg(all(
         any(feature = "v1", feature = "v2"),
@@ -145,19 +168,24 @@ pub async fn validate_create_request(
         })
         .attach_printable("Profile id is a mandatory parameter")?;
 
-    Ok((payout_id, payout_method_data, profile_id))
+    Ok((payout_id, payout_method_data, profile_id, customer))
 }
 
-pub fn validate_payout_link_request(confirm: Option<bool>) -> Result<(), errors::ApiErrorResponse> {
-    if let Some(cnf) = confirm {
-        if cnf {
-            return Err(errors::ApiErrorResponse::InvalidRequestData {
-                message: "cannot confirm a payout while creating a payout link".to_string(),
-            });
-        } else {
-            return Ok(());
-        }
+pub fn validate_payout_link_request(
+    req: &payouts::PayoutCreateRequest,
+) -> Result<(), errors::ApiErrorResponse> {
+    if req.confirm.unwrap_or(false) {
+        return Err(errors::ApiErrorResponse::InvalidRequestData {
+            message: "cannot confirm a payout while creating a payout link".to_string(),
+        });
     }
+
+    if req.customer_id.is_none() {
+        return Err(errors::ApiErrorResponse::MissingRequiredField {
+            field_name: "customer or customer_id when payout_link is true",
+        });
+    }
+
     Ok(())
 }
 
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index 9e102e61e16..b997aad1080 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -1889,7 +1889,7 @@ impl PayoutsInterface for KafkaStore {
         Vec<(
             storage::Payouts,
             storage::PayoutAttempt,
-            diesel_models::Customer,
+            Option<diesel_models::Customer>,
         )>,
         errors::DataStorageError,
     > {
diff --git a/crates/router/src/routes/payouts.rs b/crates/router/src/routes/payouts.rs
index 02e67e66c81..3c3d0f0a16b 100644
--- a/crates/router/src/routes/payouts.rs
+++ b/crates/router/src/routes/payouts.rs
@@ -14,18 +14,6 @@ use crate::{
 };
 
 /// Payouts - Create
-#[utoipa::path(
-    post,
-    path = "/payouts/create",
-    request_body=PayoutCreateRequest,
-    responses(
-        (status = 200, description = "Payout created", body = PayoutCreateResponse),
-        (status = 400, description = "Missing Mandatory fields")
-    ),
-    tag = "Payouts",
-    operation_id = "Create a Payout",
-    security(("api_key" = []))
-)]
 #[instrument(skip_all, fields(flow = ?Flow::PayoutsCreate))]
 pub async fn payouts_create(
     state: web::Data<AppState>,
@@ -47,20 +35,6 @@ pub async fn payouts_create(
     .await
 }
 /// Payouts - Retrieve
-#[utoipa::path(
-    get,
-    path = "/payouts/{payout_id}",
-    params(
-        ("payout_id" = String, Path, description = "The identifier for payout]")
-    ),
-    responses(
-        (status = 200, description = "Payout retrieved", body = PayoutCreateResponse),
-        (status = 404, description = "Payout does not exist in our records")
-    ),
-    tag = "Payouts",
-    operation_id = "Retrieve a Payout",
-    security(("api_key" = []))
-)]
 #[instrument(skip_all, fields(flow = ?Flow::PayoutsRetrieve))]
 pub async fn payouts_retrieve(
     state: web::Data<AppState>,
@@ -98,21 +72,6 @@ pub async fn payouts_retrieve(
     .await
 }
 /// Payouts - Update
-#[utoipa::path(
-    post,
-    path = "/payouts/{payout_id}",
-    params(
-        ("payout_id" = String, Path, description = "The identifier for payout]")
-    ),
-    request_body=PayoutCreateRequest,
-    responses(
-        (status = 200, description = "Payout updated", body = PayoutCreateResponse),
-        (status = 400, description = "Missing Mandatory fields")
-    ),
-    tag = "Payouts",
-    operation_id = "Update a Payout",
-    security(("api_key" = []))
-)]
 #[instrument(skip_all, fields(flow = ?Flow::PayoutsUpdate))]
 pub async fn payouts_update(
     state: web::Data<AppState>,
@@ -170,22 +129,8 @@ pub async fn payouts_confirm(
     ))
     .await
 }
+
 /// Payouts - Cancel
-#[utoipa::path(
-    post,
-    path = "/payouts/{payout_id}/cancel",
-    params(
-        ("payout_id" = String, Path, description = "The identifier for payout")
-    ),
-    request_body=PayoutActionRequest,
-    responses(
-        (status = 200, description = "Payout cancelled", body = PayoutCreateResponse),
-        (status = 400, description = "Missing Mandatory fields")
-    ),
-    tag = "Payouts",
-    operation_id = "Cancel a Payout",
-    security(("api_key" = []))
-)]
 #[instrument(skip_all, fields(flow = ?Flow::PayoutsCancel))]
 pub async fn payouts_cancel(
     state: web::Data<AppState>,
@@ -211,21 +156,6 @@ pub async fn payouts_cancel(
     .await
 }
 /// Payouts - Fulfill
-#[utoipa::path(
-    post,
-    path = "/payouts/{payout_id}/fulfill",
-    params(
-        ("payout_id" = String, Path, description = "The identifier for payout")
-    ),
-    request_body=PayoutActionRequest,
-    responses(
-        (status = 200, description = "Payout fulfilled", body = PayoutCreateResponse),
-        (status = 400, description = "Missing Mandatory fields")
-    ),
-    tag = "Payouts",
-    operation_id = "Fulfill a Payout",
-    security(("api_key" = []))
-)]
 #[instrument(skip_all, fields(flow = ?Flow::PayoutsFulfill))]
 pub async fn payouts_fulfill(
     state: web::Data<AppState>,
@@ -253,17 +183,6 @@ pub async fn payouts_fulfill(
 
 /// Payouts - List
 #[cfg(feature = "olap")]
-#[utoipa::path(
-    get,
-    path = "/payouts/list",
-    responses(
-        (status = 200, description = "Payouts listed", body = PayoutListResponse),
-        (status = 404, description = "Payout not found")
-    ),
-    tag = "Payouts",
-    operation_id = "List payouts",
-    security(("api_key" = []))
-)]
 #[instrument(skip_all, fields(flow = ?Flow::PayoutsList))]
 pub async fn payouts_list(
     state: web::Data<AppState>,
@@ -293,17 +212,6 @@ pub async fn payouts_list(
 
 /// Payouts - Filtered list
 #[cfg(feature = "olap")]
-#[utoipa::path(
-    post,
-    path = "/payouts/list",
-    responses(
-        (status = 200, description = "Payouts filtered", body = PayoutListResponse),
-        (status = 404, description = "Payout not found")
-    ),
-    tag = "Payouts",
-    operation_id = "Filter payouts",
-    security(("api_key" = []))
-)]
 #[instrument(skip_all, fields(flow = ?Flow::PayoutsList))]
 pub async fn payouts_list_by_filter(
     state: web::Data<AppState>,
@@ -333,17 +241,6 @@ pub async fn payouts_list_by_filter(
 
 /// Payouts - Available filters
 #[cfg(feature = "olap")]
-#[utoipa::path(
-    post,
-    path = "/payouts/filter",
-    responses(
-        (status = 200, description = "Payouts filtered", body = PayoutListFilters),
-        (status = 404, description = "Payout not found")
-    ),
-    tag = "Payouts",
-    operation_id = "Filter payouts",
-    security(("api_key" = []))
-)]
 #[instrument(skip_all, fields(flow = ?Flow::PayoutsFilter))]
 pub async fn payouts_list_available_filters(
     state: web::Data<AppState>,
diff --git a/crates/router/src/services/kafka/payout.rs b/crates/router/src/services/kafka/payout.rs
index 655639ed913..ca89a34f18d 100644
--- a/crates/router/src/services/kafka/payout.rs
+++ b/crates/router/src/services/kafka/payout.rs
@@ -8,8 +8,8 @@ pub struct KafkaPayout<'a> {
     pub payout_id: &'a String,
     pub payout_attempt_id: &'a String,
     pub merchant_id: &'a id_type::MerchantId,
-    pub customer_id: &'a id_type::CustomerId,
-    pub address_id: &'a String,
+    pub customer_id: Option<&'a id_type::CustomerId>,
+    pub address_id: Option<&'a String>,
     pub profile_id: &'a String,
     pub payout_method_id: Option<&'a String>,
     pub payout_type: Option<storage_enums::PayoutType>,
@@ -46,8 +46,8 @@ impl<'a> KafkaPayout<'a> {
             payout_id: &payouts.payout_id,
             payout_attempt_id: &payout_attempt.payout_attempt_id,
             merchant_id: &payouts.merchant_id,
-            customer_id: &payouts.customer_id,
-            address_id: &payouts.address_id,
+            customer_id: payouts.customer_id.as_ref(),
+            address_id: payouts.address_id.as_ref(),
             profile_id: &payouts.profile_id,
             payout_method_id: payouts.payout_method_id.as_ref(),
             payout_type: payouts.payout_type,
diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs
index 04889e191b1..6b156098d5e 100644
--- a/crates/router/src/types/api/payments.rs
+++ b/crates/router/src/types/api/payments.rs
@@ -1,8 +1,8 @@
 pub use api_models::payments::{
     AcceptanceType, Address, AddressDetails, Amount, AuthenticationForStartResponse, Card,
-    CryptoData, CustomerAcceptance, HeaderPayload, MandateAmountData, MandateData,
-    MandateTransactionType, MandateType, MandateValidationFields, NextActionType, OnlineMandate,
-    OpenBankingSessionToken, PayLaterData, PaymentIdType, PaymentListConstraints,
+    CryptoData, CustomerAcceptance, CustomerDetailsResponse, HeaderPayload, MandateAmountData,
+    MandateData, MandateTransactionType, MandateType, MandateValidationFields, NextActionType,
+    OnlineMandate, OpenBankingSessionToken, PayLaterData, PaymentIdType, PaymentListConstraints,
     PaymentListFilterConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentListResponse,
     PaymentListResponseV2, PaymentMethodData, PaymentMethodDataRequest, PaymentMethodDataResponse,
     PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsApproveRequest,
diff --git a/crates/router/src/types/api/payouts.rs b/crates/router/src/types/api/payouts.rs
index 0e89950d69e..84b7d93d68a 100644
--- a/crates/router/src/types/api/payouts.rs
+++ b/crates/router/src/types/api/payouts.rs
@@ -1,8 +1,9 @@
 pub use api_models::payouts::{
     AchBankTransfer, BacsBankTransfer, Bank as BankPayout, CardPayout, PayoutActionRequest,
-    PayoutCreateRequest, PayoutCreateResponse, PayoutListConstraints, PayoutListFilterConstraints,
-    PayoutListFilters, PayoutListResponse, PayoutMethodData, PayoutRequest, PayoutRetrieveBody,
-    PayoutRetrieveRequest, PixBankTransfer, SepaBankTransfer, Wallet as WalletPayout,
+    PayoutAttemptResponse, PayoutCreateRequest, PayoutCreateResponse, PayoutLinkResponse,
+    PayoutListConstraints, PayoutListFilterConstraints, PayoutListFilters, PayoutListResponse,
+    PayoutMethodData, PayoutRequest, PayoutRetrieveBody, PayoutRetrieveRequest, PixBankTransfer,
+    SepaBankTransfer, Wallet as WalletPayout,
 };
 pub use hyperswitch_domain_models::router_flow_types::payouts::{
     PoCancel, PoCreate, PoEligibility, PoFulfill, PoQuote, PoRecipient, PoRecipientAccount, PoSync,
diff --git a/crates/router/src/types/api/payouts_v2.rs b/crates/router/src/types/api/payouts_v2.rs
index 00a451e772b..8db2b1692b3 100644
--- a/crates/router/src/types/api/payouts_v2.rs
+++ b/crates/router/src/types/api/payouts_v2.rs
@@ -1,8 +1,9 @@
 pub use api_models::payouts::{
     AchBankTransfer, BacsBankTransfer, Bank as BankPayout, CardPayout, PayoutActionRequest,
-    PayoutCreateRequest, PayoutCreateResponse, PayoutListConstraints, PayoutListFilterConstraints,
-    PayoutListFilters, PayoutListResponse, PayoutMethodData, PayoutRequest, PayoutRetrieveBody,
-    PayoutRetrieveRequest, PixBankTransfer, SepaBankTransfer, Wallet as WalletPayout,
+    PayoutAttemptResponse, PayoutCreateRequest, PayoutCreateResponse, PayoutListConstraints,
+    PayoutListFilterConstraints, PayoutListFilters, PayoutListResponse, PayoutMethodData,
+    PayoutRequest, PayoutRetrieveBody, PayoutRetrieveRequest, PixBankTransfer, SepaBankTransfer,
+    Wallet as WalletPayout,
 };
 pub use hyperswitch_domain_models::router_flow_types::payouts::{
     PoCancel, PoCreate, PoEligibility, PoFulfill, PoQuote, PoRecipient, PoRecipientAccount, PoSync,
diff --git a/crates/storage_impl/src/mock_db/payouts.rs b/crates/storage_impl/src/mock_db/payouts.rs
index 53d34a39a28..835f73c62de 100644
--- a/crates/storage_impl/src/mock_db/payouts.rs
+++ b/crates/storage_impl/src/mock_db/payouts.rs
@@ -69,7 +69,8 @@ impl PayoutsInterface for MockDb {
         _merchant_id: &common_utils::id_type::MerchantId,
         _filters: &hyperswitch_domain_models::payouts::PayoutFetchConstraints,
         _storage_scheme: storage_enums::MerchantStorageScheme,
-    ) -> CustomResult<Vec<(Payouts, PayoutAttempt, diesel_models::Customer)>, StorageError> {
+    ) -> CustomResult<Vec<(Payouts, PayoutAttempt, Option<diesel_models::Customer>)>, StorageError>
+    {
         // TODO: Implement function for `MockDb`
         Err(StorageError::MockDbError)?
     }
diff --git a/crates/storage_impl/src/payouts/payout_attempt.rs b/crates/storage_impl/src/payouts/payout_attempt.rs
index b17ba1b6d7f..8d2d62f2d37 100644
--- a/crates/storage_impl/src/payouts/payout_attempt.rs
+++ b/crates/storage_impl/src/payouts/payout_attempt.rs
@@ -619,9 +619,13 @@ impl DataModelExt for PayoutAttemptUpdate {
             Self::BusinessUpdate {
                 business_country,
                 business_label,
+                address_id,
+                customer_id,
             } => DieselPayoutAttemptUpdate::BusinessUpdate {
                 business_country,
                 business_label,
+                address_id,
+                customer_id,
             },
             Self::UpdateRouting {
                 connector,
diff --git a/crates/storage_impl/src/payouts/payouts.rs b/crates/storage_impl/src/payouts/payouts.rs
index c72965f0106..87433006f22 100644
--- a/crates/storage_impl/src/payouts/payouts.rs
+++ b/crates/storage_impl/src/payouts/payouts.rs
@@ -8,7 +8,7 @@ use common_utils::ext_traits::Encode;
 ))]
 use diesel::JoinOnDsl;
 #[cfg(feature = "olap")]
-use diesel::{associations::HasTable, ExpressionMethods, QueryDsl};
+use diesel::{associations::HasTable, ExpressionMethods, NullableExpressionMethods, QueryDsl};
 #[cfg(feature = "olap")]
 use diesel_models::{
     customers::Customer as DieselCustomer, query::generics::db_metrics,
@@ -47,7 +47,13 @@ use router_env::logger;
 use router_env::{instrument, tracing};
 
 #[cfg(feature = "olap")]
-use crate::connection;
+use crate::{
+    connection,
+    store::schema::{
+        customers::all_columns as cust_all_columns, payout_attempt::all_columns as poa_all_columns,
+        payouts::all_columns as po_all_columns,
+    },
+};
 use crate::{
     diesel_error_to_data_error,
     errors::RedisErrorExt,
@@ -320,7 +326,7 @@ impl<T: DatabaseStore> PayoutsInterface for KVRouterStore<T> {
         merchant_id: &common_utils::id_type::MerchantId,
         filters: &PayoutFetchConstraints,
         storage_scheme: MerchantStorageScheme,
-    ) -> error_stack::Result<Vec<(Payouts, PayoutAttempt, diesel_models::Customer)>, StorageError>
+    ) -> error_stack::Result<Vec<(Payouts, PayoutAttempt, Option<DieselCustomer>)>, StorageError>
     {
         self.router_store
             .filter_payouts_and_attempts(merchant_id, filters, storage_scheme)
@@ -541,7 +547,8 @@ impl<T: DatabaseStore> PayoutsInterface for crate::RouterStore<T> {
         merchant_id: &common_utils::id_type::MerchantId,
         filters: &PayoutFetchConstraints,
         storage_scheme: MerchantStorageScheme,
-    ) -> error_stack::Result<Vec<(Payouts, PayoutAttempt, DieselCustomer)>, StorageError> {
+    ) -> error_stack::Result<Vec<(Payouts, PayoutAttempt, Option<DieselCustomer>)>, StorageError>
+    {
         use common_utils::errors::ReportSwitchExt;
 
         let conn = connection::pg_connection_read(self).await.switch()?;
@@ -553,7 +560,7 @@ impl<T: DatabaseStore> PayoutsInterface for crate::RouterStore<T> {
             )
             .inner_join(
                 diesel_models::schema::customers::table
-                    .on(cust_dsl::customer_id.eq(po_dsl::customer_id)),
+                    .on(cust_dsl::customer_id.nullable().eq(po_dsl::customer_id)),
             )
             .filter(po_dsl::merchant_id.eq(merchant_id.to_owned()))
             .order(po_dsl::created_at.desc())
@@ -645,7 +652,8 @@ impl<T: DatabaseStore> PayoutsInterface for crate::RouterStore<T> {
         logger::debug!(filter = %diesel::debug_query::<diesel::pg::Pg,_>(&query).to_string());
 
         query
-            .get_results_async::<(DieselPayouts, DieselPayoutAttempt, DieselCustomer)>(conn)
+            .select((po_all_columns, poa_all_columns, cust_all_columns.nullable()))
+            .get_results_async::<(DieselPayouts, DieselPayoutAttempt, Option<DieselCustomer>)>(conn)
             .await
             .map(|results| {
                 results
@@ -836,6 +844,8 @@ impl DataModelExt for PayoutsUpdate {
                 status,
                 confirm,
                 payout_type,
+                address_id,
+                customer_id,
             } => DieselPayoutsUpdate::Update {
                 amount,
                 destination_currency,
@@ -850,6 +860,8 @@ impl DataModelExt for PayoutsUpdate {
                 status,
                 confirm,
                 payout_type,
+                address_id,
+                customer_id,
             },
             Self::PayoutMethodIdUpdate { payout_method_id } => {
                 DieselPayoutsUpdate::PayoutMethodIdUpdate { payout_method_id }
diff --git a/migrations/2024-07-31-063531_alter_customer_id_in_payouts/down.sql b/migrations/2024-07-31-063531_alter_customer_id_in_payouts/down.sql
new file mode 100644
index 00000000000..41672976078
--- /dev/null
+++ b/migrations/2024-07-31-063531_alter_customer_id_in_payouts/down.sql
@@ -0,0 +1,15 @@
+ALTER TABLE payouts
+ALTER COLUMN customer_id
+SET
+    NOT NULL,
+ALTER COLUMN address_id
+SET
+    NOT NULL;
+
+ALTER TABLE payout_attempt
+ALTER COLUMN customer_id
+SET
+    NOT NULL,
+ALTER COLUMN address_id
+SET
+    NOT NULL;
\ No newline at end of file
diff --git a/migrations/2024-07-31-063531_alter_customer_id_in_payouts/up.sql b/migrations/2024-07-31-063531_alter_customer_id_in_payouts/up.sql
new file mode 100644
index 00000000000..310989a5160
--- /dev/null
+++ b/migrations/2024-07-31-063531_alter_customer_id_in_payouts/up.sql
@@ -0,0 +1,11 @@
+ALTER TABLE payouts
+ALTER COLUMN customer_id
+DROP NOT NULL,
+ALTER COLUMN address_id
+DROP NOT NULL;
+
+ALTER TABLE payout_attempt
+ALTER COLUMN customer_id
+DROP NOT NULL,
+ALTER COLUMN address_id
+DROP NOT NULL;
\ No newline at end of file

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

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

## Description
This PR includes below set of changes

- move customer specific fields to `customer` object for payout creation (backwards compatible)
  - `customer` to be used when creating new customers
  - `customer_id` to be used to specify details of an already onboarded customer
- update API reference docs - `PolymorphicSchema` derivation for payout APIs
- `billing_address` made optional for payout creation

### Additional Changes

- [x] 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`
-->

## How did you test it?
*Backward comptability for customer fields*

<details>
  <summary>1. Create a new customer (without passing customer_id)</summary>
  curl --location 'http://localhost:8080/payouts/create' \
    --header 'Content-Type: application/json' \
    --header 'api-key: dev_OkApivBrWlFwhoXRY6Iulr3QONuGY9ZcGrBkXu2T9P0RijlIkM4ch0Xa1iuWQXQO' \
    --data-raw '{
        "amount": 100,
        "currency": "EUR",
        "profile_id": "pro_oVAk8tgH9W7ffFnsZqe0",
        "email": "new_cust@example.com",
        "name": "John Doe",
        "phone": "999999999",
        "phone_country_code": "+65",
        "connector": [
            "adyen"
        ],
        "description": "Its my first payout request",
        "payout_type": "card",
        "payout_method_data": {
            "card": {
                "card_number": "4111111111111111",
                "expiry_month": "3",
                "expiry_year": "2030",
                "card_holder_name": "John Doe"
            }
        },
        "billing": {
            "address": {
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "city": "San Fransico",
                "state": "CA",
                "zip": "94122",
                "country": "US",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            }
        },
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "confirm": true,
        "auto_fulfill": true
    }'

![image](https://github.com/user-attachments/assets/86040ba4-93d2-46e8-9f4e-7e3b6fcc037f)
</details>

<details>
  <summary>2. Create a new customer (pass customer_id)</summary>
  curl --location 'http://localhost:8080/payouts/create' \
    --header 'Content-Type: application/json' \
    --header 'api-key: dev_OkApivBrWlFwhoXRY6Iulr3QONuGY9ZcGrBkXu2T9P0RijlIkM4ch0Xa1iuWQXQO' \
    --data-raw '{
        "amount": 100,
        "currency": "EUR",
        "profile_id": "pro_oVAk8tgH9W7ffFnsZqe0",
        "email": "new_cust@example.com",
        "customer_id": "mycustomer_id",
        "name": "John Doe",
        "phone": "999999999",
        "phone_country_code": "+65",
        "connector": [
            "adyen"
        ],
        "description": "Its my first payout request",
        "payout_type": "card",
        "payout_method_data": {
            "card": {
                "card_number": "4111111111111111",
                "expiry_month": "3",
                "expiry_year": "2030",
                "card_holder_name": "John Doe"
            }
        },
        "billing": {
            "address": {
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "city": "San Fransico",
                "state": "CA",
                "zip": "94122",
                "country": "US",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            }
        },
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "confirm": true,
        "auto_fulfill": true
    }'

![image](https://github.com/user-attachments/assets/10473cb7-2c8f-4834-bf82-5fb94165c941)
</details>

<details>
  <summary>3. Attach existing customer (using customer_id)</summary>
  curl --location 'http://localhost:8080/payouts/create' \
    --header 'Content-Type: application/json' \
    --header 'api-key: dev_OkApivBrWlFwhoXRY6Iulr3QONuGY9ZcGrBkXu2T9P0RijlIkM4ch0Xa1iuWQXQO' \
    --data '{
        "amount": 100,
        "currency": "EUR",
        "profile_id": "pro_oVAk8tgH9W7ffFnsZqe0",
        "customer_id": "mycustomer_id",
        "connector": [
            "adyen"
        ],
        "description": "Its my first payout request",
        "payout_type": "card",
        "payout_method_data": {
            "card": {
                "card_number": "4111111111111111",
                "expiry_month": "3",
                "expiry_year": "2030",
                "card_holder_name": "John Doe"
            }
        },
        "billing": {
            "address": {
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "city": "San Fransico",
                "state": "CA",
                "zip": "94122",
                "country": "US",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            }
        },
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "confirm": true,
        "auto_fulfill": true
    }'

![image](https://github.com/user-attachments/assets/d47fcb5c-1b91-4e28-a787-1787580d7ebe)
</details>

*Updated consumption of customer*

<details>
  <summary>1. Create a new customer</summary>
  curl --location 'http://localhost:8080/payouts/create' \
    --header 'Content-Type: application/json' \
    --header 'api-key: dev_OkApivBrWlFwhoXRY6Iulr3QONuGY9ZcGrBkXu2T9P0RijlIkM4ch0Xa1iuWQXQO' \
    --data-raw '{
        "amount": 100,
        "currency": "EUR",
        "profile_id": "pro_oVAk8tgH9W7ffFnsZqe0",
        "customer": {
            "id": "updated_customer_id",
            "email": "new_cust@example.com",
            "name": "John Doe",
            "phone": "999999999",
            "phone_country_code": "+65"
        },
        "connector": [
            "adyen"
        ],
        "description": "Its my first payout request",
        "payout_type": "card",
        "payout_method_data": {
            "card": {
                "card_number": "4111111111111111",
                "expiry_month": "3",
                "expiry_year": "2030",
                "card_holder_name": "John Doe"
            }
        },
        "billing": {
            "address": {
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "city": "San Fransico",
                "state": "CA",
                "zip": "94122",
                "country": "US",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            }
        },
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "confirm": true,
        "auto_fulfill": true
    }'

![image](https://github.com/user-attachments/assets/85ff8a4d-1581-4421-999a-e81328f63a32)
</details>

<details>
  <summary>2. Use existing customer</summary>
  curl --location 'http://localhost:8080/payouts/create' \
    --header 'Content-Type: application/json' \
    --header 'api-key: dev_OkApivBrWlFwhoXRY6Iulr3QONuGY9ZcGrBkXu2T9P0RijlIkM4ch0Xa1iuWQXQO' \
    --data '{
        "amount": 100,
        "currency": "EUR",
        "profile_id": "pro_oVAk8tgH9W7ffFnsZqe0",
        "customer_id": "updated_customer_id",
        "connector": [
            "adyen"
        ],
        "description": "Its my first payout request",
        "payout_type": "card",
        "payout_method_data": {
            "card": {
                "card_number": "4111111111111111",
                "expiry_month": "3",
                "expiry_year": "2030",
                "card_holder_name": "John Doe"
            }
        },
        "billing": {
            "address": {
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "city": "San Fransico",
                "state": "CA",
                "zip": "94122",
                "country": "US",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            }
        },
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "confirm": true,
        "auto_fulfill": true
    }'
</details>

*Billing address consumption*
<details>
<summary>1. Create a payout without billing.address and customer</summary>
    curl --location 'http://localhost:8080/payouts/create' \
        --header 'x-feature: integ-custom' \
        --header 'Content-Type: application/json' \
        --header 'api-key: dev_OkApivBrWlFwhoXRY6Iulr3QONuGY9ZcGrBkXu2T9P0RijlIkM4ch0Xa1iuWQXQO' \
        --data '{
            "amount": 1,
            "currency": "EUR",
            "description": "Its my first payout request",
            "payout_type": "bank",
            "entity_type": "NaturalPerson",
            "recurring": true,
            "metadata": {
                "ref": "123"
            },
            "confirm": false,
            "auto_fulfill": false
        }'

![image](https://github.com/user-attachments/assets/faf23a6b-ac8b-4a2c-a30d-7adba08495e0)
</details>

<details>
<summary>2. Update payout with billing.address and customer details</summary>
    curl --location --request PUT 'http://localhost:8080/payouts/e682bd59-3a12-4a8f-9a74-29f07055fb2c' \
        --header 'Content-Type: application/json' \
        --header 'api-key: dev_OkApivBrWlFwhoXRY6Iulr3QONuGY9ZcGrBkXu2T9P0RijlIkM4ch0Xa1iuWQXQO' \
        --data '{
            "billing": {
                "address": {
                    "line1": "1467",
                    "line2": "Harrison Street",
                    "line3": "Harrison Street",
                    "city": "San Fransico",
                    "state": "CA",
                    "zip": "94122",
                    "country": "US",
                    "first_name": "John",
                    "last_name": "Doe"
                },
                "phone": {
                    "number": "8056594427",
                    "country_code": "+91"
                }
            },
            "customer_id": "cus_05845u0qSuOENDnHLqTl"
        }'

![image](https://github.com/user-attachments/assets/d9d67427-540a-480e-9a7c-21396b3d26ad)
</details>

<details>
<summary>3. Confirm / fulfill payout</summary>
    curl --location 'http://localhost:8080/payouts/e2f0e7c8-1b23-47b6-959b-7fe8c4c938dc/confirm' \
        --header 'Content-Type: application/json' \
        --header 'api-key: dev_OkApivBrWlFwhoXRY6Iulr3QONuGY9ZcGrBkXu2T9P0RijlIkM4ch0Xa1iuWQXQO' \
        --data '{
            "payout_method_data": {
                "bank": {
                    "iban": "NL46TEST0136169112",
                    "bic": "ABNANL2A",
                    "bank_name": "Deutsche Bank",
                    "bank_country_code": "NL",
                    "bank_city": "Amsterdam"
                }
            }
        }'

![image](https://github.com/user-attachments/assets/1484657d-8354-4cd7-b978-cb9d84239436)
</details>



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