feat(core): add backwards compatibility for multiple mca (#866)

This commit is contained in:
Narayan Bhat
2023-04-13 17:04:36 +05:30
committed by GitHub
parent aebb4dca66
commit cf902f19e5
11 changed files with 287 additions and 151 deletions

View File

@ -22,6 +22,7 @@ kv_store = []
accounts_cache = []
openapi = ["olap", "oltp"]
vergen = ["router_env/vergen"]
multiple_mca = ["api_models/multiple_mca"]
[dependencies]

View File

@ -1,7 +1,6 @@
use api_models::admin::PrimaryBusinessDetails;
use common_utils::ext_traits::ValueExt;
use error_stack::{report, FutureExt, IntoReport, ResultExt};
use masking::ExposeInterface;
use storage_models::{enums, merchant_account};
use uuid::Uuid;
@ -13,13 +12,12 @@ use crate::{
payments::helpers,
},
db::StorageInterface,
pii::Secret,
routes::AppState,
services::api as service_api,
types::{
self, api,
storage::{self, MerchantAccount},
transformers::{ForeignInto, ForeignTryInto},
transformers::{ForeignInto, ForeignTryFrom, ForeignTryInto},
},
utils::{self, OptionExt},
};
@ -33,6 +31,31 @@ pub fn create_merchant_publishable_key() -> String {
)
}
fn get_primary_business_details(
request: &api::MerchantAccountCreate,
) -> Vec<PrimaryBusinessDetails> {
// In this case, business details is not optional, it will always be passed
#[cfg(feature = "multiple_mca")]
{
request.primary_business_details.to_owned()
}
// In this case, business details will be optional, if it is not passed, then create the
// default value
#[cfg(not(feature = "multiple_mca"))]
{
request
.primary_business_details
.to_owned()
.unwrap_or_else(|| {
vec![PrimaryBusinessDetails {
country: enums::CountryCode::US,
business: "default".to_string(),
}]
})
}
}
pub async fn create_merchant_account(
state: &AppState,
req: api::MerchantAccountCreate,
@ -66,36 +89,33 @@ pub async fn create_merchant_account(
.attach_printable("Unexpected create API key response"),
}?;
let merchant_details = req
.merchant_details
.map(|md| {
utils::Encode::<api::MerchantDetails>::encode_to_value(&md).change_context(
errors::ApiErrorResponse::InvalidDataValue {
field_name: "merchant_details",
},
)
})
.transpose()?;
let primary_business_details =
utils::Encode::<api::WebhookDetails>::encode_to_value(&get_primary_business_details(&req))
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "primary_business_details",
})?;
let webhook_details = req
.webhook_details
.map(|wd| {
utils::Encode::<api::WebhookDetails>::encode_to_value(&wd).change_context(
errors::ApiErrorResponse::InvalidDataValue {
field_name: "webhook details",
},
)
})
.transpose()?;
let merchant_details =
req.merchant_details
.as_ref()
.map(|merchant_details| {
utils::Encode::<api::MerchantDetails>::encode_to_value(merchant_details)
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "merchant_details",
})
})
.transpose()?;
let primary_business_details = req.primary_business_details.expose();
let _valid_business_details: PrimaryBusinessDetails = primary_business_details
.clone()
.parse_value("primary_business_details")
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "primary_business_details",
})?;
let webhook_details =
req.webhook_details
.as_ref()
.map(|webhook_details| {
utils::Encode::<api::WebhookDetails>::encode_to_value(webhook_details)
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "webhook details",
})
})
.transpose()?;
if let Some(ref routing_algorithm) = req.routing_algorithm {
let _: api::RoutingAlgorithm = routing_algorithm
@ -139,7 +159,11 @@ pub async fn create_merchant_account(
})?;
Ok(service_api::ApplicationResponse::Json(
merchant_account.foreign_into(),
ForeignTryFrom::foreign_try_from(merchant_account).change_context(
errors::ApiErrorResponse::InvalidDataValue {
field_name: "merchant_account",
},
)?,
))
}
@ -155,7 +179,11 @@ pub async fn get_merchant_account(
})?;
Ok(service_api::ApplicationResponse::Json(
merchant_account.foreign_into(),
ForeignTryFrom::foreign_try_from(merchant_account).change_context(
errors::ApiErrorResponse::InvalidDataValue {
field_name: "merchant_account",
},
)?,
))
}
@ -236,7 +264,11 @@ pub async fn merchant_account_update(
})?;
Ok(service_api::ApplicationResponse::Json(
response.foreign_into(),
ForeignTryFrom::foreign_try_from(response).change_context(
errors::ApiErrorResponse::InvalidDataValue {
field_name: "merchant_account",
},
)?,
))
}
@ -295,27 +327,49 @@ async fn validate_merchant_id<S: Into<String>>(
})
}
fn get_business_details_wrapper(
request: &api::MerchantConnectorCreate,
_merchant_account: &MerchantAccount,
) -> RouterResult<(enums::CountryCode, String)> {
#[cfg(feature = "multiple_mca")]
{
// The fields are mandatory
Ok((request.business_country, request.business_label.to_owned()))
}
#[cfg(not(feature = "multiple_mca"))]
{
// If the value is not passed, then take it from Merchant account
helpers::get_business_details(
request.business_country,
request.business_label.as_ref(),
_merchant_account,
)
}
}
pub async fn create_payment_connector(
store: &dyn StorageInterface,
req: api::MerchantConnector,
req: api::MerchantConnectorCreate,
merchant_id: &String,
) -> RouterResponse<api::MerchantConnector> {
let _merchant_account = store
) -> RouterResponse<api_models::admin::MerchantConnectorResponse> {
let merchant_account = store
.find_merchant_account_by_merchant_id(merchant_id)
.await
.map_err(|error| {
error.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)
})?;
let (business_country, business_label) = get_business_details_wrapper(&req, &merchant_account)?;
let connector_label = helpers::get_connector_label(
req.business_country,
&req.business_label,
business_country,
&business_label,
req.business_sub_label.as_ref(),
&req.connector_name,
);
let mut vec = Vec::new();
let mut response = req.clone();
let payment_methods_enabled = match req.payment_methods_enabled {
Some(val) => {
for pm in val.into_iter() {
@ -352,8 +406,8 @@ pub async fn create_payment_connector(
disabled: req.disabled,
metadata: req.metadata,
connector_label: connector_label.clone(),
business_country: req.business_country,
business_label: req.business_label,
business_country,
business_label,
business_sub_label: req.business_sub_label,
};
@ -364,19 +418,16 @@ pub async fn create_payment_connector(
error.to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantConnectorAccount)
})?;
response.merchant_connector_id = Some(mca.merchant_connector_id);
response.connector_label = connector_label;
response.business_country = mca.business_country;
response.business_label = mca.business_label;
let mca_response = ForeignTryFrom::foreign_try_from(mca)?;
Ok(service_api::ApplicationResponse::Json(response))
Ok(service_api::ApplicationResponse::Json(mca_response))
}
pub async fn retrieve_payment_connector(
store: &dyn StorageInterface,
merchant_id: String,
merchant_connector_id: String,
) -> RouterResponse<api::MerchantConnector> {
) -> RouterResponse<api_models::admin::MerchantConnectorResponse> {
let _merchant_account = store
.find_merchant_account_by_merchant_id(&merchant_id)
.await
@ -395,14 +446,14 @@ pub async fn retrieve_payment_connector(
})?;
Ok(service_api::ApplicationResponse::Json(
mca.foreign_try_into()?,
ForeignTryFrom::foreign_try_from(mca)?,
))
}
pub async fn list_payment_connectors(
store: &dyn StorageInterface,
merchant_id: String,
) -> RouterResponse<Vec<api::MerchantConnector>> {
) -> RouterResponse<Vec<api_models::admin::MerchantConnectorResponse>> {
// Validate merchant account
store
.find_merchant_account_by_merchant_id(&merchant_id)
@ -432,7 +483,7 @@ pub async fn update_payment_connector(
merchant_id: &str,
merchant_connector_id: &str,
req: api_models::admin::MerchantConnectorUpdate,
) -> RouterResponse<api::MerchantConnector> {
) -> RouterResponse<api_models::admin::MerchantConnectorResponse> {
let _merchant_account = db
.find_merchant_account_by_merchant_id(merchant_id)
.await
@ -478,32 +529,9 @@ pub async fn update_payment_connector(
format!("Failed while updating MerchantConnectorAccount: id: {merchant_connector_id}")
})?;
let updated_pm_enabled = updated_mca.payment_methods_enabled.map(|pm| {
pm.into_iter()
.flat_map(|pm_value| {
ValueExt::<api_models::admin::PaymentMethodsEnabled>::parse_value(
pm_value,
"PaymentMethods",
)
})
.collect::<Vec<api_models::admin::PaymentMethodsEnabled>>()
});
let mca_response = ForeignTryFrom::foreign_try_from(updated_mca)?;
let response = api::MerchantConnector {
connector_type: updated_mca.connector_type.foreign_into(),
connector_name: updated_mca.connector_name,
merchant_connector_id: Some(updated_mca.merchant_connector_id),
connector_account_details: Some(Secret::new(updated_mca.connector_account_details)),
test_mode: updated_mca.test_mode,
disabled: updated_mca.disabled,
payment_methods_enabled: updated_pm_enabled,
metadata: updated_mca.metadata,
connector_label: updated_mca.connector_label,
business_country: updated_mca.business_country,
business_label: updated_mca.business_label,
business_sub_label: updated_mca.business_sub_label,
};
Ok(service_api::ApplicationResponse::Json(response))
Ok(service_api::ApplicationResponse::Json(mca_response))
}
pub async fn delete_payment_connector(

View File

@ -1285,14 +1285,14 @@ pub fn get_business_details(
business_country: Option<api_enums::CountryCode>,
business_label: Option<&String>,
merchant_account: &storage_models::merchant_account::MerchantAccount,
) -> Result<(api_enums::CountryCode, String), error_stack::Report<errors::ApiErrorResponse>> {
) -> RouterResult<(api_enums::CountryCode, String)> {
let (business_country, business_label) = match business_country.zip(business_label) {
Some((business_country, business_label)) => {
(business_country.to_owned(), business_label.to_owned())
}
None => {
// Parse the primary business details from merchant account
let primary_business_details: api_models::admin::PrimaryBusinessDetails =
let primary_business_details: Vec<api_models::admin::PrimaryBusinessDetails> =
merchant_account
.primary_business_details
.clone()
@ -1300,28 +1300,20 @@ pub fn get_business_details(
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("failed to parse primary business details")?;
if primary_business_details.country.len() == 1
&& primary_business_details.business.len() == 1
{
let primary_business_country = primary_business_details
.country
.first()
.get_required_value("business_country")?
.to_owned();
let primary_business_label = primary_business_details
.business
.first()
.get_required_value("business_label")?
.to_owned();
if primary_business_details.len() == 1 {
let primary_business_details = primary_business_details.first().ok_or(
errors::ApiErrorResponse::MissingRequiredField {
field_name: "primary_business_details",
},
)?;
(
business_country.unwrap_or(primary_business_country),
business_country.unwrap_or(primary_business_details.country.to_owned()),
business_label
.map(ToString::to_string)
.unwrap_or(primary_business_label),
.unwrap_or(primary_business_details.business.to_owned()),
)
} else {
// If primary business details are not present or more than one
Err(report!(errors::ApiErrorResponse::MissingRequiredField {
field_name: "business_country, business_label"
}))?

View File

@ -149,7 +149,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::enums::DisputeStage,
api_models::enums::DisputeStatus,
api_models::enums::CountryCode,
api_models::admin::MerchantConnector,
api_models::admin::MerchantConnectorCreate,
api_models::admin::PaymentMethodsEnabled,
api_models::disputes::DisputeResponse,
api_models::payments::AddressDetails,

View File

@ -172,7 +172,7 @@ pub async fn payment_connector_create(
state: web::Data<AppState>,
req: HttpRequest,
path: web::Path<String>,
json_payload: web::Json<admin::MerchantConnector>,
json_payload: web::Json<admin::MerchantConnectorCreate>,
) -> HttpResponse {
let flow = Flow::MerchantConnectorsCreate;
let merchant_id = path.into_inner();

View File

@ -1,17 +1,25 @@
pub use api_models::admin::{
MerchantAccountCreate, MerchantAccountDeleteResponse, MerchantAccountResponse,
MerchantAccountUpdate, MerchantConnector, MerchantConnectorDeleteResponse,
MerchantAccountUpdate, MerchantConnectorCreate, MerchantConnectorDeleteResponse,
MerchantConnectorDetails, MerchantConnectorDetailsWrap, MerchantConnectorId, MerchantDetails,
MerchantId, PaymentMethodsEnabled, RoutingAlgorithm, ToggleKVRequest, ToggleKVResponse,
WebhookDetails,
};
use common_utils::ext_traits::ValueExt;
use crate::types::{storage, transformers::ForeignFrom};
use crate::{
core::errors,
types::{storage, transformers::ForeignTryFrom},
};
impl ForeignFrom<storage::MerchantAccount> for MerchantAccountResponse {
fn foreign_from(value: storage::MerchantAccount) -> Self {
let item = value;
Self {
impl ForeignTryFrom<storage::MerchantAccount> for MerchantAccountResponse {
type Error = error_stack::Report<errors::ParsingError>;
fn foreign_try_from(item: storage::MerchantAccount) -> Result<Self, Self::Error> {
let primary_business_details: Vec<api_models::admin::PrimaryBusinessDetails> = item
.primary_business_details
.parse_value("primary_business_details")?;
Ok(Self {
merchant_id: item.merchant_id,
merchant_name: item.merchant_name,
api_key: item.api_key,
@ -27,7 +35,7 @@ impl ForeignFrom<storage::MerchantAccount> for MerchantAccountResponse {
publishable_key: item.publishable_key,
metadata: item.metadata,
locker_id: item.locker_id,
primary_business_details: item.primary_business_details.into(),
}
primary_business_details,
})
}
}

View File

@ -1,6 +1,7 @@
use api_models::enums as api_enums;
use common_utils::ext_traits::ValueExt;
use error_stack::ResultExt;
use masking::Secret;
use storage_models::enums as storage_enums;
use crate::{
@ -336,37 +337,6 @@ impl<'a> ForeignFrom<&'a storage::Address> for api_types::Address {
}
}
impl ForeignTryFrom<storage::MerchantConnectorAccount> for api_models::admin::MerchantConnector {
type Error = error_stack::Report<errors::ApiErrorResponse>;
fn foreign_try_from(item: storage::MerchantConnectorAccount) -> Result<Self, Self::Error> {
let merchant_ca = item;
let payment_methods_enabled = match merchant_ca.payment_methods_enabled {
Some(val) => serde_json::Value::Array(val)
.parse_value("PaymentMethods")
.change_context(errors::ApiErrorResponse::InternalServerError)?,
None => None,
};
Ok(Self {
connector_type: merchant_ca.connector_type.foreign_into(),
connector_name: merchant_ca.connector_name,
merchant_connector_id: Some(merchant_ca.merchant_connector_id),
connector_account_details: Some(masking::Secret::new(
merchant_ca.connector_account_details,
)),
test_mode: merchant_ca.test_mode,
disabled: merchant_ca.disabled,
metadata: merchant_ca.metadata,
payment_methods_enabled,
connector_label: merchant_ca.connector_label,
business_country: merchant_ca.business_country,
business_label: merchant_ca.business_label,
business_sub_label: merchant_ca.business_sub_label,
})
}
}
impl ForeignFrom<api_models::enums::PaymentMethodType>
for storage_models::enums::PaymentMethodType
{
@ -551,3 +521,34 @@ impl ForeignFrom<storage_models::cards_info::CardInfo>
}
}
}
impl ForeignTryFrom<storage_models::merchant_connector_account::MerchantConnectorAccount>
for api_models::admin::MerchantConnectorResponse
{
type Error = error_stack::Report<errors::ApiErrorResponse>;
fn foreign_try_from(
item: storage_models::merchant_connector_account::MerchantConnectorAccount,
) -> Result<Self, Self::Error> {
let payment_methods_enabled = match item.payment_methods_enabled {
Some(val) => serde_json::Value::Array(val)
.parse_value("PaymentMethods")
.change_context(errors::ApiErrorResponse::InternalServerError)?,
None => None,
};
Ok(Self {
connector_type: item.connector_type.foreign_into(),
connector_name: item.connector_name,
connector_label: item.connector_label,
merchant_connector_id: item.merchant_connector_id,
connector_account_details: Secret::new(item.connector_account_details),
test_mode: item.test_mode,
disabled: item.disabled,
payment_methods_enabled,
metadata: item.metadata,
business_country: item.business_country,
business_label: item.business_label,
business_sub_label: item.business_sub_label,
})
}
}