mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	feat(payment_methods): populate connector_customer during customer creation step in payment methods migrate flow (#8319)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -16,12 +16,10 @@ use masking::PeekInterface; | ||||
| use serde::de; | ||||
| use utoipa::{schema, ToSchema}; | ||||
|  | ||||
| #[cfg(feature = "v1")] | ||||
| use crate::customers; | ||||
| #[cfg(feature = "payouts")] | ||||
| use crate::payouts; | ||||
| use crate::{ | ||||
|     admin, enums as api_enums, | ||||
|     admin, customers, enums as api_enums, | ||||
|     payments::{self, BankCodeResponse}, | ||||
| }; | ||||
|  | ||||
| @ -2485,6 +2483,7 @@ pub struct PaymentMethodRecord { | ||||
|     pub payment_method_type: Option<api_enums::PaymentMethodType>, | ||||
|     pub nick_name: masking::Secret<String>, | ||||
|     pub payment_instrument_id: Option<masking::Secret<String>>, | ||||
|     pub connector_customer_id: Option<String>, | ||||
|     pub card_number_masked: masking::Secret<String>, | ||||
|     pub card_expiry_month: masking::Secret<String>, | ||||
|     pub card_expiry_year: masking::Secret<String>, | ||||
| @ -2510,6 +2509,18 @@ pub struct PaymentMethodRecord { | ||||
|     pub network_token_requestor_ref_id: Option<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] | ||||
| pub struct ConnectorCustomerDetails { | ||||
|     pub connector_customer_id: String, | ||||
|     pub merchant_connector_id: id_type::MerchantConnectorAccountId, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] | ||||
| pub struct PaymentMethodCustomerMigrate { | ||||
|     pub customer: customers::CustomerRequest, | ||||
|     pub connector_customer_details: Option<ConnectorCustomerDetails>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, serde::Serialize)] | ||||
| pub struct PaymentMethodMigrationResponse { | ||||
|     pub line_number: Option<i64>, | ||||
| @ -2678,29 +2689,40 @@ impl | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "v1")] | ||||
| impl From<(PaymentMethodRecord, id_type::MerchantId)> for customers::CustomerRequest { | ||||
| impl From<(PaymentMethodRecord, id_type::MerchantId)> for PaymentMethodCustomerMigrate { | ||||
|     fn from(value: (PaymentMethodRecord, id_type::MerchantId)) -> Self { | ||||
|         let (record, merchant_id) = value; | ||||
|         Self { | ||||
|             customer_id: Some(record.customer_id), | ||||
|             merchant_id, | ||||
|             name: record.name, | ||||
|             email: record.email, | ||||
|             phone: record.phone, | ||||
|             description: None, | ||||
|             phone_country_code: record.phone_country_code, | ||||
|             address: Some(payments::AddressDetails { | ||||
|                 city: Some(record.billing_address_city), | ||||
|                 country: record.billing_address_country, | ||||
|                 line1: Some(record.billing_address_line1), | ||||
|                 line2: record.billing_address_line2, | ||||
|                 state: Some(record.billing_address_state), | ||||
|                 line3: record.billing_address_line3, | ||||
|                 zip: Some(record.billing_address_zip), | ||||
|                 first_name: Some(record.billing_address_first_name), | ||||
|                 last_name: Some(record.billing_address_last_name), | ||||
|             }), | ||||
|             metadata: None, | ||||
|             customer: customers::CustomerRequest { | ||||
|                 customer_id: Some(record.customer_id), | ||||
|                 merchant_id, | ||||
|                 name: record.name, | ||||
|                 email: record.email, | ||||
|                 phone: record.phone, | ||||
|                 description: None, | ||||
|                 phone_country_code: record.phone_country_code, | ||||
|                 address: Some(payments::AddressDetails { | ||||
|                     city: Some(record.billing_address_city), | ||||
|                     country: record.billing_address_country, | ||||
|                     line1: Some(record.billing_address_line1), | ||||
|                     line2: record.billing_address_line2, | ||||
|                     state: Some(record.billing_address_state), | ||||
|                     line3: record.billing_address_line3, | ||||
|                     zip: Some(record.billing_address_zip), | ||||
|                     first_name: Some(record.billing_address_first_name), | ||||
|                     last_name: Some(record.billing_address_last_name), | ||||
|                 }), | ||||
|                 metadata: None, | ||||
|             }, | ||||
|             connector_customer_details: record | ||||
|                 .connector_customer_id | ||||
|                 .zip(record.merchant_connector_id) | ||||
|                 .map( | ||||
|                     |(connector_customer_id, merchant_connector_id)| ConnectorCustomerDetails { | ||||
|                         connector_customer_id, | ||||
|                         merchant_connector_id, | ||||
|                     }, | ||||
|                 ), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -9,6 +9,16 @@ pub struct ConnectorCustomerMap( | ||||
|     std::collections::HashMap<common_utils::id_type::MerchantConnectorAccountId, String>, | ||||
| ); | ||||
|  | ||||
| #[cfg(feature = "v2")] | ||||
| impl ConnectorCustomerMap { | ||||
|     /// Creates a new `ConnectorCustomerMap` from a HashMap | ||||
|     pub fn new( | ||||
|         map: std::collections::HashMap<common_utils::id_type::MerchantConnectorAccountId, String>, | ||||
|     ) -> Self { | ||||
|         Self(map) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "v2")] | ||||
| common_utils::impl_to_sql_from_sql_json!(ConnectorCustomerMap); | ||||
|  | ||||
|  | ||||
| @ -57,7 +57,7 @@ pub async fn customer_create( | ||||
|             let merchant_context = domain::MerchantContext::NormalMerchant(Box::new( | ||||
|                 domain::Context(auth.merchant_account, auth.key_store), | ||||
|             )); | ||||
|             customers::create_customer(state, merchant_context, req) | ||||
|             customers::create_customer(state, merchant_context, req, None) | ||||
|         }, | ||||
|         &auth::HeaderAuth(auth::ApiKeyAuth { | ||||
|             is_connected_allowed: false, | ||||
|  | ||||
| @ -27,7 +27,7 @@ use crate::{ | ||||
|     routes::{metrics, SessionState}, | ||||
|     services, | ||||
|     types::{ | ||||
|         api::customers, | ||||
|         api::{customers, payment_methods as payment_methods_api}, | ||||
|         domain::{self, types}, | ||||
|         storage::{self, enums}, | ||||
|         transformers::ForeignFrom, | ||||
| @ -41,6 +41,7 @@ pub async fn create_customer( | ||||
|     state: SessionState, | ||||
|     merchant_context: domain::MerchantContext, | ||||
|     customer_data: customers::CustomerRequest, | ||||
|     connector_customer_details: Option<payment_methods_api::ConnectorCustomerDetails>, | ||||
| ) -> errors::CustomerResponse<customers::CustomerResponse> { | ||||
|     let db: &dyn StorageInterface = state.store.as_ref(); | ||||
|     let key_manager_state = &(&state).into(); | ||||
| @ -69,6 +70,7 @@ pub async fn create_customer( | ||||
|  | ||||
|     let domain_customer = customer_data | ||||
|         .create_domain_model_from_request( | ||||
|             &connector_customer_details, | ||||
|             db, | ||||
|             &merchant_reference_id, | ||||
|             &merchant_context, | ||||
| @ -94,6 +96,7 @@ pub async fn create_customer( | ||||
| trait CustomerCreateBridge { | ||||
|     async fn create_domain_model_from_request<'a>( | ||||
|         &'a self, | ||||
|         connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>, | ||||
|         db: &'a dyn StorageInterface, | ||||
|         merchant_reference_id: &'a Option<id_type::CustomerId>, | ||||
|         merchant_context: &'a domain::MerchantContext, | ||||
| @ -112,6 +115,7 @@ trait CustomerCreateBridge { | ||||
| impl CustomerCreateBridge for customers::CustomerRequest { | ||||
|     async fn create_domain_model_from_request<'a>( | ||||
|         &'a self, | ||||
|         connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>, | ||||
|         db: &'a dyn StorageInterface, | ||||
|         merchant_reference_id: &'a Option<id_type::CustomerId>, | ||||
|         merchant_context: &'a domain::MerchantContext, | ||||
| @ -171,6 +175,15 @@ impl CustomerCreateBridge for customers::CustomerRequest { | ||||
|             domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) | ||||
|                 .change_context(errors::CustomersErrorResponse::InternalServerError)?; | ||||
|  | ||||
|         let connector_customer = connector_customer_details.as_ref().map(|details| { | ||||
|             let merchant_connector_id = details.merchant_connector_id.get_string_repr().to_string(); | ||||
|             let connector_customer_id = details.connector_customer_id.to_string(); | ||||
|             let object = serde_json::json!({ | ||||
|                 merchant_connector_id: connector_customer_id | ||||
|             }); | ||||
|             pii::SecretSerdeValue::new(object) | ||||
|         }); | ||||
|  | ||||
|         Ok(domain::Customer { | ||||
|             customer_id: merchant_reference_id | ||||
|                 .to_owned() | ||||
| @ -188,7 +201,7 @@ impl CustomerCreateBridge for customers::CustomerRequest { | ||||
|             description: self.description.clone(), | ||||
|             phone_country_code: self.phone_country_code.clone(), | ||||
|             metadata: self.metadata.clone(), | ||||
|             connector_customer: None, | ||||
|             connector_customer, | ||||
|             address_id: address_from_db.clone().map(|addr| addr.address_id), | ||||
|             created_at: common_utils::date_time::now(), | ||||
|             modified_at: common_utils::date_time::now(), | ||||
| @ -214,6 +227,7 @@ impl CustomerCreateBridge for customers::CustomerRequest { | ||||
| impl CustomerCreateBridge for customers::CustomerRequest { | ||||
|     async fn create_domain_model_from_request<'a>( | ||||
|         &'a self, | ||||
|         connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>, | ||||
|         _db: &'a dyn StorageInterface, | ||||
|         merchant_reference_id: &'a Option<id_type::CustomerId>, | ||||
|         merchant_context: &'a domain::MerchantContext, | ||||
| @ -283,6 +297,15 @@ impl CustomerCreateBridge for customers::CustomerRequest { | ||||
|             domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) | ||||
|                 .change_context(errors::CustomersErrorResponse::InternalServerError)?; | ||||
|  | ||||
|         let connector_customer = connector_customer_details.as_ref().map(|details| { | ||||
|             let mut map = std::collections::HashMap::new(); | ||||
|             map.insert( | ||||
|                 details.merchant_connector_id.clone(), | ||||
|                 details.connector_customer_id.to_string(), | ||||
|             ); | ||||
|             common_types::customers::ConnectorCustomerMap::new(map) | ||||
|         }); | ||||
|  | ||||
|         Ok(domain::Customer { | ||||
|             id: id_type::GlobalCustomerId::generate(&state.conf.cell_information.id), | ||||
|             merchant_reference_id: merchant_reference_id.to_owned(), | ||||
| @ -299,7 +322,7 @@ impl CustomerCreateBridge for customers::CustomerRequest { | ||||
|             description: self.description.clone(), | ||||
|             phone_country_code: self.phone_country_code.clone(), | ||||
|             metadata: self.metadata.clone(), | ||||
|             connector_customer: None, | ||||
|             connector_customer, | ||||
|             created_at: common_utils::date_time::now(), | ||||
|             modified_at: common_utils::date_time::now(), | ||||
|             default_payment_method_id: None, | ||||
| @ -1024,6 +1047,7 @@ pub async fn update_customer( | ||||
|     let updated_customer = update_customer | ||||
|         .request | ||||
|         .create_domain_model_from_request( | ||||
|             &None, | ||||
|             db, | ||||
|             &merchant_context, | ||||
|             key_manager_state, | ||||
| @ -1039,6 +1063,7 @@ pub async fn update_customer( | ||||
| trait CustomerUpdateBridge { | ||||
|     async fn create_domain_model_from_request<'a>( | ||||
|         &'a self, | ||||
|         connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>, | ||||
|         db: &'a dyn StorageInterface, | ||||
|         merchant_context: &'a domain::MerchantContext, | ||||
|         key_manager_state: &'a KeyManagerState, | ||||
| @ -1211,6 +1236,7 @@ impl VerifyIdForUpdateCustomer<'_> { | ||||
| impl CustomerUpdateBridge for customers::CustomerUpdateRequest { | ||||
|     async fn create_domain_model_from_request<'a>( | ||||
|         &'a self, | ||||
|         _connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>, | ||||
|         db: &'a dyn StorageInterface, | ||||
|         merchant_context: &'a domain::MerchantContext, | ||||
|         key_manager_state: &'a KeyManagerState, | ||||
| @ -1315,6 +1341,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { | ||||
| impl CustomerUpdateBridge for customers::CustomerUpdateRequest { | ||||
|     async fn create_domain_model_from_request<'a>( | ||||
|         &'a self, | ||||
|         connector_customer_details: &'a Option<payment_methods_api::ConnectorCustomerDetails>, | ||||
|         db: &'a dyn StorageInterface, | ||||
|         merchant_context: &'a domain::MerchantContext, | ||||
|         key_manager_state: &'a KeyManagerState, | ||||
| @ -1432,11 +1459,18 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { | ||||
|  | ||||
| pub async fn migrate_customers( | ||||
|     state: SessionState, | ||||
|     customers: Vec<customers::CustomerRequest>, | ||||
|     customers_migration: Vec<payment_methods_api::PaymentMethodCustomerMigrate>, | ||||
|     merchant_context: domain::MerchantContext, | ||||
| ) -> errors::CustomerResponse<()> { | ||||
|     for customer in customers { | ||||
|         match create_customer(state.clone(), merchant_context.clone(), customer).await { | ||||
|     for customer_migration in customers_migration { | ||||
|         match create_customer( | ||||
|             state.clone(), | ||||
|             merchant_context.clone(), | ||||
|             customer_migration.customer, | ||||
|             customer_migration.connector_customer_details, | ||||
|         ) | ||||
|         .await | ||||
|         { | ||||
|             Ok(_) => (), | ||||
|             Err(e) => match e.current_context() { | ||||
|                 errors::CustomersErrorResponse::CustomerAlreadyExists => (), | ||||
|  | ||||
| @ -25,7 +25,7 @@ pub async fn customers_create( | ||||
|             let merchant_context = domain::MerchantContext::NormalMerchant(Box::new( | ||||
|                 domain::Context(auth.merchant_account, auth.key_store), | ||||
|             )); | ||||
|             create_customer(state, merchant_context, req) | ||||
|             create_customer(state, merchant_context, req, None) | ||||
|         }, | ||||
|         auth::auth_type( | ||||
|             &auth::V2ApiKeyAuth { | ||||
| @ -58,7 +58,7 @@ pub async fn customers_create( | ||||
|             let merchant_context = domain::MerchantContext::NormalMerchant(Box::new( | ||||
|                 domain::Context(auth.merchant_account, auth.key_store), | ||||
|             )); | ||||
|             create_customer(state, merchant_context, req) | ||||
|             create_customer(state, merchant_context, req, None) | ||||
|         }, | ||||
|         auth::auth_type( | ||||
|             &auth::HeaderAuth(auth::ApiKeyAuth { | ||||
|  | ||||
| @ -14,6 +14,8 @@ use hyperswitch_domain_models::{ | ||||
| use router_env::{instrument, logger, tracing, Flow}; | ||||
|  | ||||
| use super::app::{AppState, SessionState}; | ||||
| #[cfg(all(feature = "v1", any(feature = "olap", feature = "oltp")))] | ||||
| use crate::core::{customers, payment_methods::tokenize}; | ||||
| use crate::{ | ||||
|     core::{ | ||||
|         api_locking, | ||||
| @ -27,11 +29,6 @@ use crate::{ | ||||
|         storage::payment_method::PaymentTokenData, | ||||
|     }, | ||||
| }; | ||||
| #[cfg(all(feature = "v1", any(feature = "olap", feature = "oltp")))] | ||||
| use crate::{ | ||||
|     core::{customers, payment_methods::tokenize}, | ||||
|     types::api::customers::CustomerRequest, | ||||
| }; | ||||
|  | ||||
| #[cfg(feature = "v1")] | ||||
| #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsCreate))] | ||||
| @ -360,7 +357,12 @@ pub async fn migrate_payment_methods( | ||||
|                 customers::migrate_customers( | ||||
|                     state.clone(), | ||||
|                     req.iter() | ||||
|                         .map(|e| CustomerRequest::from((e.clone(), merchant_id.clone()))) | ||||
|                         .map(|e| { | ||||
|                             payment_methods::PaymentMethodCustomerMigrate::from(( | ||||
|                                 e.clone(), | ||||
|                                 merchant_id.clone(), | ||||
|                             )) | ||||
|                         }) | ||||
|                         .collect(), | ||||
|                     merchant_context.clone(), | ||||
|                 ) | ||||
|  | ||||
| @ -1,30 +1,32 @@ | ||||
| #[cfg(feature = "v2")] | ||||
| pub use api_models::payment_methods::{ | ||||
|     CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardNetworkTokenizeRequest, | ||||
|     CardNetworkTokenizeResponse, CardType, CustomerPaymentMethodResponseItem, | ||||
|     DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, GetTokenizePayloadResponse, | ||||
|     ListCountriesCurrenciesRequest, MigrateCardDetail, NetworkTokenDetailsPaymentMethod, | ||||
|     NetworkTokenDetailsResponse, NetworkTokenResponse, PaymentMethodCollectLinkRenderRequest, | ||||
|     PaymentMethodCollectLinkRequest, PaymentMethodCreate, PaymentMethodCreateData, | ||||
|     PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodIntentConfirm, | ||||
|     PaymentMethodIntentCreate, PaymentMethodListData, PaymentMethodListRequest, | ||||
|     PaymentMethodListResponseForSession, PaymentMethodMigrate, PaymentMethodMigrateResponse, | ||||
|     PaymentMethodResponse, PaymentMethodResponseData, PaymentMethodUpdate, PaymentMethodUpdateData, | ||||
|     PaymentMethodsData, TokenDataResponse, TokenDetailsResponse, TokenizePayloadEncrypted, | ||||
|     TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, | ||||
|     TokenizedWalletValue2, TotalPaymentMethodCountResponse, | ||||
|     CardNetworkTokenizeResponse, CardType, ConnectorCustomerDetails, | ||||
|     CustomerPaymentMethodResponseItem, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, | ||||
|     GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, MigrateCardDetail, | ||||
|     NetworkTokenDetailsPaymentMethod, NetworkTokenDetailsResponse, NetworkTokenResponse, | ||||
|     PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate, | ||||
|     PaymentMethodCreateData, PaymentMethodCustomerMigrate, PaymentMethodDeleteResponse, | ||||
|     PaymentMethodId, PaymentMethodIntentConfirm, PaymentMethodIntentCreate, PaymentMethodListData, | ||||
|     PaymentMethodListRequest, PaymentMethodListResponseForSession, PaymentMethodMigrate, | ||||
|     PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodResponseData, | ||||
|     PaymentMethodUpdate, PaymentMethodUpdateData, PaymentMethodsData, TokenDataResponse, | ||||
|     TokenDetailsResponse, TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, | ||||
|     TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2, | ||||
|     TotalPaymentMethodCountResponse, | ||||
| }; | ||||
| #[cfg(feature = "v1")] | ||||
| pub use api_models::payment_methods::{ | ||||
|     CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardNetworkTokenizeRequest, | ||||
|     CardNetworkTokenizeResponse, CustomerPaymentMethod, CustomerPaymentMethodsListResponse, | ||||
|     DefaultPaymentMethod, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, | ||||
|     GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, MigrateCardDetail, | ||||
|     PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate, | ||||
|     PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, | ||||
|     PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodMigrate, | ||||
|     PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, | ||||
|     TokenizeCardRequest, TokenizeDataRequest, TokenizePayloadEncrypted, TokenizePayloadRequest, | ||||
|     CardNetworkTokenizeResponse, ConnectorCustomerDetails, CustomerPaymentMethod, | ||||
|     CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest, | ||||
|     GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, | ||||
|     MigrateCardDetail, PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, | ||||
|     PaymentMethodCreate, PaymentMethodCreateData, PaymentMethodCustomerMigrate, | ||||
|     PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodListRequest, | ||||
|     PaymentMethodListResponse, PaymentMethodMigrate, PaymentMethodMigrateResponse, | ||||
|     PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, TokenizeCardRequest, | ||||
|     TokenizeDataRequest, TokenizePayloadEncrypted, TokenizePayloadRequest, | ||||
|     TokenizePaymentMethodRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, | ||||
|     TokenizedWalletValue2, | ||||
| }; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Kashif
					Kashif