mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 09:07:09 +08:00
refactor(payments_v2): create customer at connector end and populate connector customer ID (#7246)
This commit is contained in:
@ -679,19 +679,20 @@ impl CustomerDeleteBridge for id_type::GlobalCustomerId {
|
||||
redacted_encrypted_value.clone().into_encrypted(),
|
||||
);
|
||||
|
||||
let updated_customer = storage::CustomerUpdate::Update {
|
||||
name: Some(redacted_encrypted_value.clone()),
|
||||
email: Box::new(Some(redacted_encrypted_email)),
|
||||
phone: Box::new(Some(redacted_encrypted_value.clone())),
|
||||
description: Some(Description::from_str_unchecked(REDACTED)),
|
||||
phone_country_code: Some(REDACTED.to_string()),
|
||||
metadata: None,
|
||||
connector_customer: Box::new(None),
|
||||
default_billing_address: None,
|
||||
default_shipping_address: None,
|
||||
default_payment_method_id: None,
|
||||
status: Some(common_enums::DeleteStatus::Redacted),
|
||||
};
|
||||
let updated_customer =
|
||||
storage::CustomerUpdate::Update(Box::new(storage::CustomerGeneralUpdate {
|
||||
name: Some(redacted_encrypted_value.clone()),
|
||||
email: Box::new(Some(redacted_encrypted_email)),
|
||||
phone: Box::new(Some(redacted_encrypted_value.clone())),
|
||||
description: Some(Description::from_str_unchecked(REDACTED)),
|
||||
phone_country_code: Some(REDACTED.to_string()),
|
||||
metadata: None,
|
||||
connector_customer: Box::new(None),
|
||||
default_billing_address: None,
|
||||
default_shipping_address: None,
|
||||
default_payment_method_id: None,
|
||||
status: Some(common_enums::DeleteStatus::Redacted),
|
||||
}));
|
||||
|
||||
db.update_customer_by_global_id(
|
||||
key_manager_state,
|
||||
@ -1338,7 +1339,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
||||
&domain_customer.id,
|
||||
domain_customer.to_owned(),
|
||||
merchant_account.get_id(),
|
||||
storage::CustomerUpdate::Update {
|
||||
storage::CustomerUpdate::Update(Box::new(storage::CustomerGeneralUpdate {
|
||||
name: encryptable_customer.name,
|
||||
email: Box::new(encryptable_customer.email.map(|email| {
|
||||
let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> =
|
||||
@ -1357,7 +1358,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest {
|
||||
default_shipping_address: encrypted_customer_shipping_address.map(Into::into),
|
||||
default_payment_method_id: Some(self.default_payment_method_id.clone()),
|
||||
status: None,
|
||||
},
|
||||
})),
|
||||
key_store,
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
|
||||
@ -3016,6 +3016,16 @@ where
|
||||
id: merchant_connector_id.get_string_repr().to_owned(),
|
||||
})?;
|
||||
|
||||
let updated_customer = call_create_connector_customer_if_required(
|
||||
state,
|
||||
customer,
|
||||
merchant_account,
|
||||
key_store,
|
||||
&merchant_connector_account,
|
||||
payment_data,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut router_data = payment_data
|
||||
.construct_router_data(
|
||||
state,
|
||||
@ -3068,8 +3078,7 @@ where
|
||||
payment_data.clone(),
|
||||
customer.clone(),
|
||||
merchant_account.storage_scheme,
|
||||
// TODO: update the customer with connector customer id
|
||||
None,
|
||||
updated_customer,
|
||||
key_store,
|
||||
frm_suggestion,
|
||||
header_payload.clone(),
|
||||
@ -3894,7 +3903,6 @@ where
|
||||
merchant_connector_account.get_mca_id(),
|
||||
)?;
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
let label = {
|
||||
let connector_label = core_utils::get_connector_label(
|
||||
payment_data.get_payment_intent().business_country,
|
||||
@ -3925,15 +3933,6 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "v2")]
|
||||
let label = {
|
||||
merchant_connector_account
|
||||
.get_mca_id()
|
||||
.get_required_value("merchant_connector_account_id")?
|
||||
.get_string_repr()
|
||||
.to_owned()
|
||||
};
|
||||
|
||||
let (should_call_connector, existing_connector_customer_id) =
|
||||
customers::should_call_connector_create_customer(
|
||||
state, &connector, customer, &label,
|
||||
@ -3961,7 +3960,90 @@ where
|
||||
let customer_update = customers::update_connector_customer_in_customers(
|
||||
&label,
|
||||
customer.as_ref(),
|
||||
&connector_customer_id,
|
||||
connector_customer_id.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
payment_data.set_connector_customer_id(connector_customer_id);
|
||||
Ok(customer_update)
|
||||
} else {
|
||||
// Customer already created in previous calls use the same value, no need to update
|
||||
payment_data.set_connector_customer_id(
|
||||
existing_connector_customer_id.map(ToOwned::to_owned),
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "v2")]
|
||||
pub async fn call_create_connector_customer_if_required<F, Req, D>(
|
||||
state: &SessionState,
|
||||
customer: &Option<domain::Customer>,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
merchant_connector_account: &domain::MerchantConnectorAccount,
|
||||
payment_data: &mut D,
|
||||
) -> RouterResult<Option<storage::CustomerUpdate>>
|
||||
where
|
||||
F: Send + Clone + Sync,
|
||||
Req: Send + Sync,
|
||||
|
||||
// To create connector flow specific interface data
|
||||
D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
|
||||
D: ConstructFlowSpecificData<F, Req, router_types::PaymentsResponseData>,
|
||||
RouterData<F, Req, router_types::PaymentsResponseData>: Feature<F, Req> + Send,
|
||||
|
||||
// To construct connector flow specific api
|
||||
dyn api::Connector:
|
||||
services::api::ConnectorIntegration<F, Req, router_types::PaymentsResponseData>,
|
||||
{
|
||||
let connector_name = payment_data.get_payment_attempt().connector.clone();
|
||||
|
||||
match connector_name {
|
||||
Some(connector_name) => {
|
||||
let connector = api::ConnectorData::get_connector_by_name(
|
||||
&state.conf.connectors,
|
||||
&connector_name,
|
||||
api::GetToken::Connector,
|
||||
Some(merchant_connector_account.get_id()),
|
||||
)?;
|
||||
|
||||
let merchant_connector_id = merchant_connector_account.get_id();
|
||||
|
||||
let (should_call_connector, existing_connector_customer_id) =
|
||||
customers::should_call_connector_create_customer(
|
||||
state,
|
||||
&connector,
|
||||
customer,
|
||||
&merchant_connector_id,
|
||||
);
|
||||
|
||||
if should_call_connector {
|
||||
// Create customer at connector and update the customer table to store this data
|
||||
let router_data = payment_data
|
||||
.construct_router_data(
|
||||
state,
|
||||
connector.connector.id(),
|
||||
merchant_account,
|
||||
key_store,
|
||||
customer,
|
||||
merchant_connector_account,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let connector_customer_id = router_data
|
||||
.create_connector_customer(state, &connector)
|
||||
.await?;
|
||||
|
||||
let customer_update = customers::update_connector_customer_in_customers(
|
||||
merchant_connector_id,
|
||||
customer.as_ref(),
|
||||
connector_customer_id.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use common_utils::pii;
|
||||
use masking::{ExposeOptionInterface, PeekInterface};
|
||||
use masking::ExposeOptionInterface;
|
||||
use router_env::{instrument, tracing};
|
||||
|
||||
use crate::{
|
||||
@ -73,17 +73,7 @@ pub async fn create_connector_customer<F: Clone, T: Clone>(
|
||||
Ok(connector_customer_id)
|
||||
}
|
||||
|
||||
pub fn get_connector_customer_details_if_present<'a>(
|
||||
customer: &'a domain::Customer,
|
||||
connector_name: &str,
|
||||
) -> Option<&'a str> {
|
||||
customer
|
||||
.connector_customer
|
||||
.as_ref()
|
||||
.and_then(|connector_customer_value| connector_customer_value.peek().get(connector_name))
|
||||
.and_then(|connector_customer| connector_customer.as_str())
|
||||
}
|
||||
|
||||
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
||||
pub fn should_call_connector_create_customer<'a>(
|
||||
state: &SessionState,
|
||||
connector: &api::ConnectorData,
|
||||
@ -98,9 +88,9 @@ pub fn should_call_connector_create_customer<'a>(
|
||||
.contains(&connector.connector_name);
|
||||
|
||||
if connector_needs_customer {
|
||||
let connector_customer_details = customer.as_ref().and_then(|customer| {
|
||||
get_connector_customer_details_if_present(customer, connector_label)
|
||||
});
|
||||
let connector_customer_details = customer
|
||||
.as_ref()
|
||||
.and_then(|customer| customer.get_connector_customer_id(connector_label));
|
||||
let should_call_connector = connector_customer_details.is_none();
|
||||
(should_call_connector, connector_customer_details)
|
||||
} else {
|
||||
@ -108,25 +98,48 @@ pub fn should_call_connector_create_customer<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
||||
pub fn should_call_connector_create_customer<'a>(
|
||||
state: &SessionState,
|
||||
connector: &api::ConnectorData,
|
||||
customer: &'a Option<domain::Customer>,
|
||||
merchant_connector_id: &common_utils::id_type::MerchantConnectorAccountId,
|
||||
) -> (bool, Option<&'a str>) {
|
||||
// Check if create customer is required for the connector
|
||||
let connector_needs_customer = state
|
||||
.conf
|
||||
.connector_customer
|
||||
.connector_list
|
||||
.contains(&connector.connector_name);
|
||||
|
||||
if connector_needs_customer {
|
||||
let connector_customer_details = customer
|
||||
.as_ref()
|
||||
.and_then(|customer| customer.get_connector_customer_id(merchant_connector_id));
|
||||
let should_call_connector = connector_customer_details.is_none();
|
||||
(should_call_connector, connector_customer_details)
|
||||
} else {
|
||||
(false, None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
||||
#[instrument]
|
||||
pub async fn update_connector_customer_in_customers(
|
||||
connector_label: &str,
|
||||
customer: Option<&domain::Customer>,
|
||||
connector_customer_id: &Option<String>,
|
||||
connector_customer_id: Option<String>,
|
||||
) -> Option<storage::CustomerUpdate> {
|
||||
let connector_customer_map = customer
|
||||
let mut connector_customer_map = customer
|
||||
.and_then(|customer| customer.connector_customer.clone().expose_option())
|
||||
.and_then(|connector_customer| connector_customer.as_object().cloned())
|
||||
.unwrap_or_default();
|
||||
|
||||
let updated_connector_customer_map =
|
||||
connector_customer_id.as_ref().map(|connector_customer_id| {
|
||||
let mut connector_customer_map = connector_customer_map;
|
||||
let connector_customer_value =
|
||||
serde_json::Value::String(connector_customer_id.to_string());
|
||||
connector_customer_map.insert(connector_label.to_string(), connector_customer_value);
|
||||
connector_customer_map
|
||||
});
|
||||
let updated_connector_customer_map = connector_customer_id.map(|connector_customer_id| {
|
||||
let connector_customer_value = serde_json::Value::String(connector_customer_id);
|
||||
connector_customer_map.insert(connector_label.to_string(), connector_customer_value);
|
||||
connector_customer_map
|
||||
});
|
||||
|
||||
updated_connector_customer_map
|
||||
.map(serde_json::Value::Object)
|
||||
@ -136,3 +149,22 @@ pub async fn update_connector_customer_in_customers(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
||||
#[instrument]
|
||||
pub async fn update_connector_customer_in_customers(
|
||||
merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
|
||||
customer: Option<&domain::Customer>,
|
||||
connector_customer_id: Option<String>,
|
||||
) -> Option<storage::CustomerUpdate> {
|
||||
connector_customer_id.map(|connector_customer_id| {
|
||||
let mut connector_customer_map = customer
|
||||
.and_then(|customer| customer.connector_customer.clone())
|
||||
.unwrap_or_default();
|
||||
connector_customer_map.insert(merchant_connector_id, connector_customer_id);
|
||||
|
||||
storage::CustomerUpdate::ConnectorCustomer {
|
||||
connector_customer: Some(connector_customer_map),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -462,6 +462,25 @@ impl<F: Clone + Sync> UpdateTracker<F, PaymentConfirmData<F>, PaymentsConfirmInt
|
||||
|
||||
payment_data.payment_attempt = updated_payment_attempt;
|
||||
|
||||
if let Some((customer, updated_customer)) = customer.zip(updated_customer) {
|
||||
let customer_id = customer.get_id().clone();
|
||||
let customer_merchant_id = customer.merchant_id.clone();
|
||||
|
||||
let _updated_customer = db
|
||||
.update_customer_by_global_id(
|
||||
key_manager_state,
|
||||
&customer_id,
|
||||
customer,
|
||||
&customer_merchant_id,
|
||||
updated_customer,
|
||||
key_store,
|
||||
storage_scheme,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to update customer during `update_trackers`")?;
|
||||
}
|
||||
|
||||
Ok((Box::new(self), payment_data))
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,6 +211,12 @@ pub async fn construct_payment_router_data_for_authorize<'a>(
|
||||
"Invalid global customer generated, not able to convert to reference id",
|
||||
)?;
|
||||
|
||||
let connector_customer_id = customer.as_ref().and_then(|customer| {
|
||||
customer
|
||||
.get_connector_customer_id(&merchant_connector_account.get_id())
|
||||
.map(String::from)
|
||||
});
|
||||
|
||||
let payment_method = payment_data.payment_attempt.payment_method_type;
|
||||
|
||||
let router_base_url = &state.base_url;
|
||||
@ -352,7 +358,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>(
|
||||
reference_id: None,
|
||||
payment_method_status: None,
|
||||
payment_method_token: None,
|
||||
connector_customer: None,
|
||||
connector_customer: connector_customer_id,
|
||||
recurring_mandate_payment_data: None,
|
||||
// TODO: This has to be generated as the reference id based on the connector configuration
|
||||
// Some connectros might not accept accept the global id. This has to be done when generating the reference id
|
||||
@ -867,6 +873,12 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>(
|
||||
"Invalid global customer generated, not able to convert to reference id",
|
||||
)?;
|
||||
|
||||
let connector_customer_id = customer.as_ref().and_then(|customer| {
|
||||
customer
|
||||
.get_connector_customer_id(&merchant_connector_account.get_id())
|
||||
.map(String::from)
|
||||
});
|
||||
|
||||
let payment_method = payment_data.payment_attempt.payment_method_type;
|
||||
|
||||
let router_base_url = &state.base_url;
|
||||
@ -994,7 +1006,7 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>(
|
||||
reference_id: None,
|
||||
payment_method_status: None,
|
||||
payment_method_token: None,
|
||||
connector_customer: None,
|
||||
connector_customer: connector_customer_id,
|
||||
recurring_mandate_payment_data: None,
|
||||
// TODO: This has to be generated as the reference id based on the connector configuration
|
||||
// Some connectros might not accept accept the global id. This has to be done when generating the reference id
|
||||
|
||||
@ -1203,6 +1203,7 @@ pub async fn complete_create_recipient(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
||||
pub async fn create_recipient(
|
||||
state: &SessionState,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
@ -1262,7 +1263,7 @@ pub async fn create_recipient(
|
||||
customers::update_connector_customer_in_customers(
|
||||
&connector_label,
|
||||
Some(&customer),
|
||||
&recipient_create_data.connector_payout_id.clone(),
|
||||
recipient_create_data.connector_payout_id.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@ -1402,6 +1403,17 @@ pub async fn create_recipient(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
||||
pub async fn create_recipient(
|
||||
state: &SessionState,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
connector_data: &api::ConnectorData,
|
||||
payout_data: &mut PayoutData,
|
||||
) -> RouterResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn complete_payout_eligibility(
|
||||
state: &SessionState,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
|
||||
@ -29,10 +29,7 @@ use crate::{
|
||||
transformers::{DataDuplicationCheck, StoreCardReq, StoreGenericReq, StoreLockerReq},
|
||||
vault,
|
||||
},
|
||||
payments::{
|
||||
customers::get_connector_customer_details_if_present, helpers as payment_helpers,
|
||||
routing, CustomerDetails,
|
||||
},
|
||||
payments::{helpers as payment_helpers, routing, CustomerDetails},
|
||||
routing::TransactionData,
|
||||
utils as core_utils,
|
||||
},
|
||||
@ -965,6 +962,7 @@ pub async fn get_default_payout_connector(
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
|
||||
pub fn should_call_payout_connector_create_customer<'a>(
|
||||
state: &'a SessionState,
|
||||
connector: &'a api::ConnectorData,
|
||||
@ -981,9 +979,39 @@ pub fn should_call_payout_connector_create_customer<'a>(
|
||||
.contains(&connector);
|
||||
|
||||
if connector_needs_customer {
|
||||
let connector_customer_details = customer.as_ref().and_then(|customer| {
|
||||
get_connector_customer_details_if_present(customer, connector_label)
|
||||
});
|
||||
let connector_customer_details = customer
|
||||
.as_ref()
|
||||
.and_then(|customer| customer.get_connector_customer_id(connector_label));
|
||||
let should_call_connector = connector_customer_details.is_none();
|
||||
(should_call_connector, connector_customer_details)
|
||||
} else {
|
||||
(false, None)
|
||||
}
|
||||
}
|
||||
_ => (false, None),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
||||
pub fn should_call_payout_connector_create_customer<'a>(
|
||||
state: &'a SessionState,
|
||||
connector: &'a api::ConnectorData,
|
||||
customer: &'a Option<domain::Customer>,
|
||||
merchant_connector_id: &'a id_type::MerchantConnectorAccountId,
|
||||
) -> (bool, Option<&'a str>) {
|
||||
// Check if create customer is required for the connector
|
||||
match enums::PayoutConnectors::try_from(connector.connector_name) {
|
||||
Ok(connector) => {
|
||||
let connector_needs_customer = state
|
||||
.conf
|
||||
.connector_customer
|
||||
.payout_connector_list
|
||||
.contains(&connector);
|
||||
|
||||
if connector_needs_customer {
|
||||
let connector_customer_details = customer
|
||||
.as_ref()
|
||||
.and_then(|customer| customer.get_connector_customer_id(merchant_connector_id));
|
||||
let should_call_connector = connector_customer_details.is_none();
|
||||
(should_call_connector, connector_customer_details)
|
||||
} else {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
pub use diesel_models::customers::{Customer, CustomerNew, CustomerUpdateInternal};
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "customer_v2"))]
|
||||
pub use crate::types::domain::CustomerGeneralUpdate;
|
||||
pub use crate::types::domain::CustomerUpdate;
|
||||
|
||||
Reference in New Issue
Block a user