mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(data-migration): add connector customer and mandate details support for multiple profiles (#8473)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -9,6 +9,7 @@ use common_utils::{
|
||||
},
|
||||
};
|
||||
use error_stack::{report, ResultExt};
|
||||
use hyperswitch_domain_models::payment_methods as payment_methods_domain;
|
||||
use masking::{ExposeInterface, Secret, SwitchStrategy};
|
||||
use payment_methods::controller::PaymentMethodsController;
|
||||
use router_env::{instrument, tracing};
|
||||
@ -27,7 +28,7 @@ use crate::{
|
||||
routes::{metrics, SessionState},
|
||||
services,
|
||||
types::{
|
||||
api::{customers, payment_methods as payment_methods_api},
|
||||
api::customers,
|
||||
domain::{self, types},
|
||||
storage::{self, enums},
|
||||
transformers::ForeignFrom,
|
||||
@ -41,7 +42,7 @@ pub async fn create_customer(
|
||||
state: SessionState,
|
||||
merchant_context: domain::MerchantContext,
|
||||
customer_data: customers::CustomerRequest,
|
||||
connector_customer_details: Option<payment_methods_api::ConnectorCustomerDetails>,
|
||||
connector_customer_details: Option<Vec<payment_methods_domain::ConnectorCustomerDetails>>,
|
||||
) -> errors::CustomerResponse<customers::CustomerResponse> {
|
||||
let db: &dyn StorageInterface = state.store.as_ref();
|
||||
let key_manager_state = &(&state).into();
|
||||
@ -96,7 +97,9 @@ 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>,
|
||||
connector_customer_details: &'a Option<
|
||||
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||
>,
|
||||
db: &'a dyn StorageInterface,
|
||||
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
||||
merchant_context: &'a domain::MerchantContext,
|
||||
@ -115,7 +118,9 @@ 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>,
|
||||
connector_customer_details: &'a Option<
|
||||
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||
>,
|
||||
db: &'a dyn StorageInterface,
|
||||
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
||||
merchant_context: &'a domain::MerchantContext,
|
||||
@ -175,13 +180,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)
|
||||
let connector_customer = connector_customer_details.as_ref().map(|details_vec| {
|
||||
let mut map = serde_json::Map::new();
|
||||
for details in details_vec {
|
||||
let merchant_connector_id =
|
||||
details.merchant_connector_id.get_string_repr().to_string();
|
||||
let connector_customer_id = details.connector_customer_id.clone();
|
||||
map.insert(merchant_connector_id, connector_customer_id.into());
|
||||
}
|
||||
pii::SecretSerdeValue::new(serde_json::Value::Object(map))
|
||||
});
|
||||
|
||||
Ok(domain::Customer {
|
||||
@ -227,7 +234,9 @@ 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>,
|
||||
connector_customer_details: &'a Option<
|
||||
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||
>,
|
||||
_db: &'a dyn StorageInterface,
|
||||
merchant_reference_id: &'a Option<id_type::CustomerId>,
|
||||
merchant_context: &'a domain::MerchantContext,
|
||||
@ -297,12 +306,16 @@ 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(),
|
||||
);
|
||||
let connector_customer = connector_customer_details.as_ref().map(|details_vec| {
|
||||
let map: std::collections::HashMap<_, _> = details_vec
|
||||
.iter()
|
||||
.map(|details| {
|
||||
(
|
||||
details.merchant_connector_id.clone(),
|
||||
details.connector_customer_id.to_string(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
common_types::customers::ConnectorCustomerMap::new(map)
|
||||
});
|
||||
|
||||
@ -1060,7 +1073,9 @@ 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>,
|
||||
connector_customer_details: &'a Option<
|
||||
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||
>,
|
||||
db: &'a dyn StorageInterface,
|
||||
merchant_context: &'a domain::MerchantContext,
|
||||
key_manager_state: &'a KeyManagerState,
|
||||
@ -1232,7 +1247,9 @@ 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>,
|
||||
_connector_customer_details: &'a Option<
|
||||
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||
>,
|
||||
db: &'a dyn StorageInterface,
|
||||
merchant_context: &'a domain::MerchantContext,
|
||||
key_manager_state: &'a KeyManagerState,
|
||||
@ -1337,7 +1354,9 @@ 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>,
|
||||
connector_customer_details: &'a Option<
|
||||
Vec<payment_methods_domain::ConnectorCustomerDetails>,
|
||||
>,
|
||||
db: &'a dyn StorageInterface,
|
||||
merchant_context: &'a domain::MerchantContext,
|
||||
key_manager_state: &'a KeyManagerState,
|
||||
@ -1454,7 +1473,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
||||
|
||||
pub async fn migrate_customers(
|
||||
state: SessionState,
|
||||
customers_migration: Vec<payment_methods_api::PaymentMethodCustomerMigrate>,
|
||||
customers_migration: Vec<payment_methods_domain::PaymentMethodCustomerMigrate>,
|
||||
merchant_context: domain::MerchantContext,
|
||||
) -> errors::CustomerResponse<()> {
|
||||
for customer_migration in customers_migration {
|
||||
|
||||
@ -10,6 +10,7 @@ use diesel_models::enums::IntentStatus;
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::{
|
||||
bulk_tokenization::CardNetworkTokenizeRequest, merchant_key_store::MerchantKeyStore,
|
||||
payment_methods::PaymentMethodCustomerMigrate, transformers::ForeignTryFrom,
|
||||
};
|
||||
use router_env::{instrument, logger, tracing, Flow};
|
||||
|
||||
@ -331,10 +332,10 @@ pub async fn migrate_payment_methods(
|
||||
MultipartForm(form): MultipartForm<migration::PaymentMethodsMigrateForm>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::PaymentMethodsMigrate;
|
||||
let (merchant_id, records, merchant_connector_id) =
|
||||
match migration::get_payment_method_records(form) {
|
||||
Ok((merchant_id, records, merchant_connector_id)) => {
|
||||
(merchant_id, records, merchant_connector_id)
|
||||
let (merchant_id, records, merchant_connector_ids) =
|
||||
match form.validate_and_get_payment_method_records() {
|
||||
Ok((merchant_id, records, merchant_connector_ids)) => {
|
||||
(merchant_id, records, merchant_connector_ids)
|
||||
}
|
||||
Err(e) => return api::log_and_return_error_response(e.into()),
|
||||
};
|
||||
@ -345,7 +346,7 @@ pub async fn migrate_payment_methods(
|
||||
records,
|
||||
|state, _, req, _| {
|
||||
let merchant_id = merchant_id.clone();
|
||||
let merchant_connector_id = merchant_connector_id.clone();
|
||||
let merchant_connector_ids = merchant_connector_ids.clone();
|
||||
async move {
|
||||
let (key_store, merchant_account) =
|
||||
get_merchant_account(&state, &merchant_id).await?;
|
||||
@ -354,20 +355,43 @@ pub async fn migrate_payment_methods(
|
||||
domain::Context(merchant_account.clone(), key_store.clone()),
|
||||
));
|
||||
|
||||
customers::migrate_customers(
|
||||
state.clone(),
|
||||
req.iter()
|
||||
.map(|e| {
|
||||
payment_methods::PaymentMethodCustomerMigrate::from((
|
||||
e.clone(),
|
||||
merchant_id.clone(),
|
||||
))
|
||||
})
|
||||
.collect(),
|
||||
merchant_context.clone(),
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let mut mca_cache = std::collections::HashMap::new();
|
||||
let customers = Vec::<PaymentMethodCustomerMigrate>::foreign_try_from((
|
||||
&req,
|
||||
merchant_id.clone(),
|
||||
))
|
||||
.map_err(|e| errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
|
||||
for record in &customers {
|
||||
if let Some(connector_customer_details) = &record.connector_customer_details {
|
||||
for connector_customer in connector_customer_details {
|
||||
if !mca_cache.contains_key(&connector_customer.merchant_connector_id) {
|
||||
let mca = state
|
||||
.store
|
||||
.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
|
||||
&(&state).into(),
|
||||
&merchant_id,
|
||||
&connector_customer.merchant_connector_id,
|
||||
merchant_context.get_merchant_key_store(),
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(
|
||||
errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
||||
id: connector_customer.merchant_connector_id.get_string_repr().to_string(),
|
||||
},
|
||||
)?;
|
||||
mca_cache
|
||||
.insert(connector_customer.merchant_connector_id.clone(), mca);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customers::migrate_customers(state.clone(), customers, merchant_context.clone())
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let controller = cards::PmCards {
|
||||
state: &state,
|
||||
merchant_context: &merchant_context,
|
||||
@ -377,7 +401,7 @@ pub async fn migrate_payment_methods(
|
||||
req,
|
||||
&merchant_id,
|
||||
&merchant_context,
|
||||
merchant_connector_id,
|
||||
merchant_connector_ids,
|
||||
&controller,
|
||||
))
|
||||
.await
|
||||
|
||||
@ -1,32 +1,30 @@
|
||||
#[cfg(feature = "v2")]
|
||||
pub use api_models::payment_methods::{
|
||||
CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardNetworkTokenizeRequest,
|
||||
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,
|
||||
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,
|
||||
};
|
||||
#[cfg(feature = "v1")]
|
||||
pub use api_models::payment_methods::{
|
||||
CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardNetworkTokenizeRequest,
|
||||
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,
|
||||
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,
|
||||
TokenizePaymentMethodRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1,
|
||||
TokenizedWalletValue2,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user