Bug: [V2]: Return connector specific customer reference ID in `CustomerResponse`



Required for tokenization flows in some connectors
diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json
index d93208fb665..67725d8c644 100644
--- a/api-reference-v2/openapi_spec.json
+++ b/api-reference-v2/openapi_spec.json
@@ -8833,6 +8833,11 @@
             "maxLength": 64,
             "minLength": 1
           },
+          "connector_customer_ids": {
+            "type": "object",
+            "description": "Connector specific customer reference ids",
+            "nullable": true
+          },
           "name": {
             "type": "string",
             "description": "The customer's name",
diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml
index 2b84c50bba9..f89558e6267 100644
--- a/crates/api_models/Cargo.toml
+++ b/crates/api_models/Cargo.toml
@@ -17,7 +17,7 @@ olap = []
 openapi = ["common_enums/openapi", "olap", "recon", "dummy_connector", "olap"]
 recon = []
 v1 = ["common_utils/v1"]
-v2 = ["common_utils/v2", "customer_v2"]
+v2 = ["common_types/v2", "common_utils/v2", "customer_v2"]
 customer_v2 = ["common_utils/customer_v2"]
 payment_methods_v2 = ["common_utils/payment_methods_v2"]
 dynamic_routing = []
diff --git a/crates/api_models/src/customers.rs b/crates/api_models/src/customers.rs
index d78564ae53a..eb61cae6e47 100644
--- a/crates/api_models/src/customers.rs
+++ b/crates/api_models/src/customers.rs
@@ -182,6 +182,9 @@ pub struct CustomerResponse {
     /// The identifier for the customer object
     #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
     pub merchant_reference_id: Option<id_type::CustomerId>,
+    /// Connector specific customer reference ids
+    #[schema(value_type = Option<Object>, example = json!({"mca_hwySG2NtpzX0qr7toOy8": "cus_Rnm2pDKGyQi506"}))]
+    pub connector_customer_ids: Option<common_types::customers::ConnectorCustomerMap>,
     /// The customer's name
     #[schema(max_length = 255, value_type = Option<String>, example = "Jon Test")]
     pub name: crypto::OptionalEncryptableName,
diff --git a/crates/common_types/src/customers.rs b/crates/common_types/src/customers.rs
new file mode 100644
index 00000000000..1d53630cade
--- /dev/null
+++ b/crates/common_types/src/customers.rs
@@ -0,0 +1,30 @@
+//! Customer related types
+
+/// HashMap containing MerchantConnectorAccountId and corresponding customer id
+#[cfg(feature = "v2")]
+#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, diesel::AsExpression)]
+#[diesel(sql_type = diesel::sql_types::Jsonb)]
+#[serde(transparent)]
+pub struct ConnectorCustomerMap(
+    std::collections::HashMap<common_utils::id_type::MerchantConnectorAccountId, String>,
+);
+
+#[cfg(feature = "v2")]
+common_utils::impl_to_sql_from_sql_json!(ConnectorCustomerMap);
+
+#[cfg(feature = "v2")]
+impl std::ops::Deref for ConnectorCustomerMap {
+    type Target =
+        std::collections::HashMap<common_utils::id_type::MerchantConnectorAccountId, String>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+#[cfg(feature = "v2")]
+impl std::ops::DerefMut for ConnectorCustomerMap {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
diff --git a/crates/common_types/src/lib.rs b/crates/common_types/src/lib.rs
index 6139ddff16a..8c4ee162a76 100644
--- a/crates/common_types/src/lib.rs
+++ b/crates/common_types/src/lib.rs
@@ -2,6 +2,7 @@
 
 #![warn(missing_docs, missing_debug_implementations)]
 
+pub mod customers;
 pub mod domain;
 pub mod payment_methods;
 pub mod payments;
diff --git a/crates/diesel_models/src/customers.rs b/crates/diesel_models/src/customers.rs
index b0d4535ddb7..24fe34c15d9 100644
--- a/crates/diesel_models/src/customers.rs
+++ b/crates/diesel_models/src/customers.rs
@@ -76,7 +76,7 @@ pub struct CustomerNew {
     pub description: Option<Description>,
     pub created_at: PrimitiveDateTime,
     pub metadata: Option<pii::SecretSerdeValue>,
-    pub connector_customer: Option<ConnectorCustomerMap>,
+    pub connector_customer: Option<common_types::customers::ConnectorCustomerMap>,
     pub modified_at: PrimitiveDateTime,
     pub default_payment_method_id: Option<common_utils::id_type::GlobalPaymentMethodId>,
     pub updated_by: Option<String>,
@@ -158,7 +158,7 @@ pub struct Customer {
     pub description: Option<Description>,
     pub created_at: PrimitiveDateTime,
     pub metadata: Option<pii::SecretSerdeValue>,
-    pub connector_customer: Option<ConnectorCustomerMap>,
+    pub connector_customer: Option<common_types::customers::ConnectorCustomerMap>,
     pub modified_at: PrimitiveDateTime,
     pub default_payment_method_id: Option<common_utils::id_type::GlobalPaymentMethodId>,
     pub updated_by: Option<String>,
@@ -236,7 +236,7 @@ pub struct CustomerUpdateInternal {
     pub phone_country_code: Option<String>,
     pub metadata: Option<pii::SecretSerdeValue>,
     pub modified_at: PrimitiveDateTime,
-    pub connector_customer: Option<ConnectorCustomerMap>,
+    pub connector_customer: Option<common_types::customers::ConnectorCustomerMap>,
     pub default_payment_method_id: Option<Option<common_utils::id_type::GlobalPaymentMethodId>>,
     pub updated_by: Option<String>,
     pub default_billing_address: Option<Encryption>,
@@ -283,31 +283,3 @@ impl CustomerUpdateInternal {
         }
     }
 }
-
-#[cfg(all(feature = "v2", feature = "customer_v2"))]
-#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, diesel::AsExpression)]
-#[diesel(sql_type = diesel::sql_types::Jsonb)]
-#[serde(transparent)]
-pub struct ConnectorCustomerMap(
-    std::collections::HashMap<common_utils::id_type::MerchantConnectorAccountId, String>,
-);
-
-#[cfg(all(feature = "v2", feature = "customer_v2"))]
-common_utils::impl_to_sql_from_sql_json!(ConnectorCustomerMap);
-
-#[cfg(all(feature = "v2", feature = "customer_v2"))]
-impl std::ops::Deref for ConnectorCustomerMap {
-    type Target =
-        std::collections::HashMap<common_utils::id_type::MerchantConnectorAccountId, String>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-#[cfg(all(feature = "v2", feature = "customer_v2"))]
-impl std::ops::DerefMut for ConnectorCustomerMap {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
diff --git a/crates/hyperswitch_domain_models/src/customer.rs b/crates/hyperswitch_domain_models/src/customer.rs
index 02c25d722c1..384bc5ab427 100644
--- a/crates/hyperswitch_domain_models/src/customer.rs
+++ b/crates/hyperswitch_domain_models/src/customer.rs
@@ -56,7 +56,7 @@ pub struct Customer {
     pub description: Option<Description>,
     pub created_at: PrimitiveDateTime,
     pub metadata: Option<pii::SecretSerdeValue>,
-    pub connector_customer: Option<diesel_models::ConnectorCustomerMap>,
+    pub connector_customer: Option<common_types::customers::ConnectorCustomerMap>,
     pub modified_at: PrimitiveDateTime,
     pub default_payment_method_id: Option<id_type::GlobalPaymentMethodId>,
     pub updated_by: Option<String>,
@@ -334,7 +334,7 @@ pub struct CustomerGeneralUpdate {
     pub description: Option<Description>,
     pub phone_country_code: Option<String>,
     pub metadata: Option<pii::SecretSerdeValue>,
-    pub connector_customer: Box<Option<diesel_models::ConnectorCustomerMap>>,
+    pub connector_customer: Box<Option<common_types::customers::ConnectorCustomerMap>>,
     pub default_billing_address: Option<Encryption>,
     pub default_shipping_address: Option<Encryption>,
     pub default_payment_method_id: Option<Option<id_type::GlobalPaymentMethodId>>,
@@ -346,7 +346,7 @@ pub struct CustomerGeneralUpdate {
 pub enum CustomerUpdate {
     Update(Box<CustomerGeneralUpdate>),
     ConnectorCustomer {
-        connector_customer: Option<diesel_models::ConnectorCustomerMap>,
+        connector_customer: Option<common_types::customers::ConnectorCustomerMap>,
     },
     UpdateDefaultPaymentMethod {
         default_payment_method_id: Option<Option<id_type::GlobalPaymentMethodId>>,
diff --git a/crates/router/src/types/api/customers.rs b/crates/router/src/types/api/customers.rs
index 5b37fb2add6..63ebfe49930 100644
--- a/crates/router/src/types/api/customers.rs
+++ b/crates/router/src/types/api/customers.rs
@@ -50,6 +50,7 @@ impl ForeignFrom<customer::Customer> for CustomerResponse {
         customers::CustomerResponse {
             id: cust.id,
             merchant_reference_id: cust.merchant_reference_id,
+            connector_customer_ids: cust.connector_customer,
             name: cust.name,
             email: cust.email,
             phone: cust.phone,

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

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

## Description
<!-- Describe your changes in detail -->
Add a field `connector_customer` to `CustomerResponse` that contains a map between `MerchantConnectorAccountId` and connector customer id

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


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->

From #7246:

> with respect to the multi-use tokens that were created with Stripe (support added in #7106), the payment methods were created on Stripe's end, but were not associated with a customer, due to which they could not be used to make transactions again, we used to receive such an error:

```text
The provided PaymentMethod cannot be attached. To reuse a PaymentMethod, you must attach it to a Customer first.
```

> To address this, we have to create a customer on Stripe's end first, and then pass Stripe's customer ID when saving the payment method with Stripe.


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->

- Request:

```
curl --location 'http://localhost:8080/v2/customers/12345_cus_01951dbc16d57d03ac193a7e297d4899' \
--header 'x-profile-id: pro_Vb32qOaqvkJClXH7aNWK' \
--header 'Authorization: api-key=dev_f4mNrhkUeCtLcsqUaG1fpzHpqTCnPdJMBSPNZAP2nWwwr7FRYtbj1RUdWBPKBKZ0' \
--header 'api-key: dev_f4mNrhkUeCtLcsqUaG1fpzHpqTCnPdJMBSPNZAP2nWwwr7FRYtbj1RUdWBPKBKZ0'
```

- Response:
```json
{
    "id": "12345_cus_01951dbc16d57d03ac193a7e297d4899",
    "merchant_reference_id": "customer_1739960621",
    "connector_customer": {
        "mca_hwySG2NtpzX0qr7toOy8": "cus_Rnm2pDKGyQi506"
    },
    "name": "John Doe",
    "email": "guest@example.com",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "First customer",
    "default_billing_address": null,
    "default_shipping_address": null,
    "created_at": "2025-02-19T10:23:40.758Z",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "default_payment_method_id": null
}
```

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

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