refactor(connector_customer): incorrect mapping of connector customer (#1275)

This commit is contained in:
Narayan Bhat
2023-05-31 20:16:39 +05:30
committed by GitHub
parent 1261791d9f
commit ebdfde75ec
5 changed files with 95 additions and 84 deletions

View File

@ -122,7 +122,6 @@ where
let updated_customer = call_create_connector_customer_if_required( let updated_customer = call_create_connector_customer_if_required(
state, state,
&payment_data.payment_attempt.connector.clone(),
&customer, &customer,
&merchant_account, &merchant_account,
&mut payment_data, &mut payment_data,
@ -639,7 +638,6 @@ where
pub async fn call_create_connector_customer_if_required<F, Req>( pub async fn call_create_connector_customer_if_required<F, Req>(
state: &AppState, state: &AppState,
connector_name: &Option<String>,
customer: &Option<domain::Customer>, customer: &Option<domain::Customer>,
merchant_account: &domain::MerchantAccount, merchant_account: &domain::MerchantAccount,
payment_data: &mut PaymentData<F>, payment_data: &mut PaymentData<F>,
@ -658,17 +656,32 @@ where
// To perform router related operation for PaymentResponse // To perform router related operation for PaymentResponse
PaymentResponse: Operation<F, Req>, PaymentResponse: Operation<F, Req>,
{ {
let connector_name = payment_data.payment_attempt.connector.clone();
match connector_name { match connector_name {
Some(connector_name) => { Some(connector_name) => {
let connector = api::ConnectorData::get_connector_by_name( let connector = api::ConnectorData::get_connector_by_name(
&state.conf.connectors, &state.conf.connectors,
connector_name, &connector_name,
api::GetToken::Connector, api::GetToken::Connector,
)?; )?;
let (is_eligible, connector_customer_id, connector_customer_map) =
customers::should_call_connector_create_customer(state, &connector, customer)?;
if is_eligible { let connector_label = helpers::get_connector_label(
payment_data.payment_intent.business_country,
&payment_data.payment_intent.business_label,
payment_data.payment_attempt.business_sub_label.as_ref(),
&connector_name,
);
let (should_call_connector, existing_connector_customer_id) =
customers::should_call_connector_create_customer(
state,
&connector,
customer,
&connector_label,
);
if should_call_connector {
// Create customer at connector and update the customer table to store this data // Create customer at connector and update the customer table to store this data
let router_data = payment_data let router_data = payment_data
.construct_router_data( .construct_router_data(
@ -679,15 +692,23 @@ where
) )
.await?; .await?;
let (connector_customer, customer_update) = router_data let connector_customer_id = router_data
.create_connector_customer(state, &connector, connector_customer_map) .create_connector_customer(state, &connector)
.await?; .await?;
payment_data.connector_customer_id = connector_customer; let customer_update = customers::update_connector_customer_in_customers(
&connector_label,
customer.as_ref(),
&connector_customer_id,
)
.await;
payment_data.connector_customer_id = connector_customer_id;
Ok(customer_update) Ok(customer_update)
} else { } else {
// Customer already created in previous calls use the same value, no need to update // Customer already created in previous calls use the same value, no need to update
payment_data.connector_customer_id = connector_customer_id; payment_data.connector_customer_id =
existing_connector_customer_id.map(ToOwned::to_owned);
Ok(None) Ok(None)
} }
} }

View File

@ -1,9 +1,8 @@
use common_utils::ext_traits::ValueExt; use router_env::{instrument, tracing};
use error_stack::{self, ResultExt};
use crate::{ use crate::{
core::{ core::{
errors::{self, ConnectorErrorExt, RouterResult}, errors::{ConnectorErrorExt, RouterResult},
payments, payments,
}, },
logger, logger,
@ -12,13 +11,13 @@ use crate::{
types::{self, api, domain, storage}, types::{self, api, domain, storage},
}; };
#[instrument(skip_all)]
pub async fn create_connector_customer<F: Clone, T: Clone>( pub async fn create_connector_customer<F: Clone, T: Clone>(
state: &AppState, state: &AppState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
router_data: &types::RouterData<F, T, types::PaymentsResponseData>, router_data: &types::RouterData<F, T, types::PaymentsResponseData>,
customer_request_data: types::ConnectorCustomerData, customer_request_data: types::ConnectorCustomerData,
connector_customer_map: Option<serde_json::Map<String, serde_json::Value>>, ) -> RouterResult<Option<String>> {
) -> RouterResult<(Option<String>, Option<storage::CustomerUpdate>)> {
let connector_integration: services::BoxedConnectorIntegration< let connector_integration: services::BoxedConnectorIntegration<
'_, '_,
api::CreateConnectorCustomer, api::CreateConnectorCustomer,
@ -68,79 +67,75 @@ pub async fn create_connector_customer<F: Clone, T: Clone>(
_ => None, _ => None,
}, },
Err(err) => { Err(err) => {
logger::debug!(payment_method_tokenization_error=?err); logger::error!(create_connector_customer_error=?err);
None None
} }
}; };
let update_customer = update_connector_customer_in_customers( Ok(connector_customer_id)
connector,
connector_customer_map,
&connector_customer_id,
)
.await?;
Ok((connector_customer_id, update_customer))
} }
type CreateCustomerCheck = ( pub fn get_connector_customer_details_if_present<'a>(
bool, customer: &'a domain::Customer,
Option<String>, connector_name: &str,
Option<serde_json::Map<String, serde_json::Value>>, ) -> Option<&'a str> {
); customer
pub fn should_call_connector_create_customer( .connector_customer
.as_ref()
.and_then(|connector_customer_value| connector_customer_value.get(connector_name))
.and_then(|connector_customer| connector_customer.as_str())
}
pub fn should_call_connector_create_customer<'a>(
state: &AppState, state: &AppState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
customer: &Option<domain::Customer>, customer: &'a Option<domain::Customer>,
) -> RouterResult<CreateCustomerCheck> { connector_label: &str,
let connector_name = connector.connector_name.to_string(); ) -> (bool, Option<&'a str>) {
//Check if create customer is required for the connector // Check if create customer is required for the connector
let connector_customer_filter = state let connector_needs_customer = state
.conf .conf
.connector_customer .connector_customer
.connector_list .connector_list
.contains(&connector.connector_name); .contains(&connector.connector_name);
if connector_customer_filter { if connector_needs_customer {
match customer { let connector_customer_details = customer.as_ref().and_then(|customer| {
Some(customer) => match &customer.connector_customer { get_connector_customer_details_if_present(customer, connector_label)
Some(connector_customer) => {
let connector_customer_map: serde_json::Map<String, serde_json::Value> =
connector_customer
.clone()
.parse_value("Map<String, Value>")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to deserialize Value to CustomerConnector")?;
let value = connector_customer_map.get(&connector_name); //Check if customer already created for this customer and for this connector
Ok((
value.is_none(),
value.and_then(|val| val.as_str().map(|cust| cust.to_string())),
Some(connector_customer_map),
))
}
None => Ok((true, None, None)),
},
None => Ok((false, None, None)),
}
} else {
Ok((false, None, None))
}
}
pub async fn update_connector_customer_in_customers(
connector: &api::ConnectorData,
connector_customer_map: Option<serde_json::Map<String, serde_json::Value>>,
connector_cust_id: &Option<String>,
) -> RouterResult<Option<storage::CustomerUpdate>> {
let mut connector_customer = match connector_customer_map {
Some(cc) => cc,
None => serde_json::Map::new(),
};
connector_cust_id.clone().map(|cc| {
connector_customer.insert(
connector.connector_name.to_string(),
serde_json::Value::String(cc),
)
}); });
Ok(Some(storage::CustomerUpdate::ConnectorCustomer { let should_call_connector = connector_customer_details.is_none();
connector_customer: Some(serde_json::Value::Object(connector_customer)), (should_call_connector, connector_customer_details)
})) } else {
(false, None)
}
}
#[instrument]
pub async fn update_connector_customer_in_customers(
connector_label: &str,
customer: Option<&domain::Customer>,
connector_customer_id: &Option<String>,
) -> Option<storage::CustomerUpdate> {
let connector_customer_map = customer
.and_then(|customer| customer.connector_customer.as_ref())
.and_then(|connector_customer| connector_customer.as_object())
.map(ToOwned::to_owned)
.unwrap_or(serde_json::Map::new());
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
});
updated_connector_customer_map
.map(serde_json::Value::Object)
.map(
|connector_customer_value| storage::CustomerUpdate::ConnectorCustomer {
connector_customer: Some(connector_customer_value),
},
)
} }

View File

@ -16,7 +16,7 @@ use crate::{
}, },
routes::AppState, routes::AppState,
services, services,
types::{self, api, domain, storage}, types::{self, api, domain},
}; };
#[async_trait] #[async_trait]
@ -87,14 +87,13 @@ pub trait Feature<F, T> {
&self, &self,
_state: &AppState, _state: &AppState,
_connector: &api::ConnectorData, _connector: &api::ConnectorData,
_connector_customer_map: Option<serde_json::Map<String, serde_json::Value>>, ) -> RouterResult<Option<String>>
) -> RouterResult<(Option<String>, Option<storage::CustomerUpdate>)>
where where
F: Clone, F: Clone,
Self: Sized, Self: Sized,
dyn api::Connector: services::ConnectorIntegration<F, T, types::PaymentsResponseData>, dyn api::Connector: services::ConnectorIntegration<F, T, types::PaymentsResponseData>,
{ {
Ok((None, None)) Ok(None)
} }
} }

View File

@ -11,7 +11,7 @@ use crate::{
logger, logger,
routes::{metrics, AppState}, routes::{metrics, AppState},
services, services,
types::{self, api, domain, storage}, types::{self, api, domain},
}; };
#[async_trait] #[async_trait]
@ -108,14 +108,12 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
&self, &self,
state: &AppState, state: &AppState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
connector_customer_map: Option<serde_json::Map<String, serde_json::Value>>, ) -> RouterResult<Option<String>> {
) -> RouterResult<(Option<String>, Option<storage::CustomerUpdate>)> {
customers::create_connector_customer( customers::create_connector_customer(
state, state,
connector, connector,
self, self,
types::ConnectorCustomerData::try_from(self)?, types::ConnectorCustomerData::try_from(self)?,
connector_customer_map,
) )
.await .await
} }

View File

@ -9,7 +9,7 @@ use crate::{
}, },
routes::AppState, routes::AppState,
services, services,
types::{self, api, domain, storage}, types::{self, api, domain},
}; };
#[async_trait] #[async_trait]
@ -84,14 +84,12 @@ impl Feature<api::Verify, types::VerifyRequestData> for types::VerifyRouterData
&self, &self,
state: &AppState, state: &AppState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
connector_customer_map: Option<serde_json::Map<String, serde_json::Value>>, ) -> RouterResult<Option<String>> {
) -> RouterResult<(Option<String>, Option<storage::CustomerUpdate>)> {
customers::create_connector_customer( customers::create_connector_customer(
state, state,
connector, connector,
self, self,
types::ConnectorCustomerData::try_from(self.request.to_owned())?, types::ConnectorCustomerData::try_from(self.request.to_owned())?,
connector_customer_map,
) )
.await .await
} }