refactor(router): refactor merchant_connector update v2 flow (#5484)

This commit is contained in:
Sai Harsha Vardhan
2024-08-05 13:38:48 +05:30
committed by GitHub
parent ec5f9de0cb
commit 9e358e4f7b
6 changed files with 497 additions and 157 deletions

View File

@ -1365,6 +1365,10 @@ impl MerchantConnectorListResponse {
}
/// Create a new Merchant Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialized services like Fraud / Accounting etc."
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "merchant_connector_account_v2")
))]
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct MerchantConnectorUpdate {
@ -1444,6 +1448,104 @@ pub struct MerchantConnectorUpdate {
pub status: Option<api_enums::ConnectorStatus>,
}
/// Create a new Merchant Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialized services like Fraud / Accounting etc."
#[cfg(all(feature = "v2", feature = "merchant_connector_account_v2"))]
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct MerchantConnectorUpdate {
/// Type of the Connector for the financial use case. Could range from Payments to Accounting to Banking.
#[schema(value_type = ConnectorType, example = "payment_processor")]
pub connector_type: api_enums::ConnectorType,
/// This is an unique label you can generate and pass in order to identify this connector account on your Hyperswitch dashboard and reports. Eg: if your profile label is `default`, connector label can be `stripe_default`
#[schema(example = "stripe_US_travel")]
pub connector_label: Option<String>,
/// An object containing the required details/credentials for a Connector account.
#[schema(value_type = Option<MerchantConnectorDetails>,example = json!({ "auth_type": "HeaderKey","api_key": "Basic MyVerySecretApiKey" }))]
pub connector_account_details: Option<pii::SecretSerdeValue>,
/// An object containing the details about the payment methods that need to be enabled under this merchant connector account
#[schema(example = json!([
{
"payment_method": "wallet",
"payment_method_types": [
"upi_collect",
"upi_intent"
],
"payment_method_issuers": [
"labore magna ipsum",
"aute"
],
"payment_schemes": [
"Discover",
"Discover"
],
"accepted_currencies": {
"type": "enable_only",
"list": ["USD", "EUR"]
},
"accepted_countries": {
"type": "disable_only",
"list": ["FR", "DE","IN"]
},
"minimum_amount": 1,
"maximum_amount": 68607706,
"recurring_enabled": true,
"installment_payment_enabled": true
}
]))]
pub payment_methods_enabled: Option<Vec<PaymentMethodsEnabled>>,
/// Webhook details of this merchant connector
#[schema(example = json!({
"connector_webhook_details": {
"merchant_secret": "1234567890987654321"
}
}))]
pub connector_webhook_details: Option<MerchantConnectorWebhookDetails>,
/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
#[schema(value_type = Option<Object>,max_length = 255,example = json!({ "city": "NY", "unit": "245" }))]
pub metadata: Option<pii::SecretSerdeValue>,
/// A boolean value to indicate if the connector is disabled. By default, its value is false.
#[schema(default = false, example = false)]
pub disabled: Option<bool>,
/// Contains the frm configs for the merchant connector
#[schema(example = json!(consts::FRM_CONFIGS_EG))]
pub frm_configs: Option<Vec<FrmConfigs>>,
/// pm_auth_config will relate MCA records to their respective chosen auth services, based on payment_method and pmt
#[schema(value_type = Option<Object>)]
pub pm_auth_config: Option<pii::SecretSerdeValue>,
#[schema(value_type = ConnectorStatus, example = "inactive")]
pub status: Option<api_enums::ConnectorStatus>,
/// The identifier for the Merchant Account
#[schema(value_type = String, max_length = 64, min_length = 1, example = "y3oqhf46pyzuxjbcn2giaqnb44")]
pub merchant_id: id_type::MerchantId,
}
#[cfg(all(feature = "v2", feature = "merchant_connector_account_v2"))]
impl MerchantConnectorUpdate {
pub fn get_frm_config_as_secret(&self) -> Option<Vec<Secret<serde_json::Value>>> {
match self.frm_configs.as_ref() {
Some(frm_value) => {
let configs_for_frm_value: Vec<Secret<serde_json::Value>> = frm_value
.iter()
.map(|config| config.encode_to_value().map(Secret::new))
.collect::<Result<Vec<_>, _>>()
.ok()?;
Some(configs_for_frm_value)
}
None => None,
}
}
}
///Details of FrmConfigs are mentioned here... it should be passed in payment connector create api call, and stored in merchant_connector_table
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(deny_unknown_fields)]

View File

@ -1,6 +1,11 @@
use std::{collections::HashMap, marker::PhantomData};
use common_utils::{errors::IntegrityCheckError, ext_traits::OptionExt, id_type, types::MinorUnit};
use common_utils::{
errors::IntegrityCheckError,
ext_traits::{OptionExt, ValueExt},
id_type,
types::MinorUnit,
};
use error_stack::ResultExt;
use masking::Secret;
@ -119,6 +124,16 @@ impl ConnectorAuthType {
"ConnectorAuthType",
))
}
pub fn from_secret_value(
value: common_utils::pii::SecretSerdeValue,
) -> common_utils::errors::CustomResult<Self, common_utils::errors::ParsingError> {
value
.parse_value::<Self>("ConnectorAuthType")
.change_context(common_utils::errors::ParsingError::StructParseFailure(
"ConnectorAuthType",
))
}
}
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]

View File

@ -1902,6 +1902,289 @@ impl<'a> MerchantDefaultConfigUpdate<'a> {
}
}
#[cfg(any(feature = "v1", feature = "v2", feature = "olap"))]
#[async_trait::async_trait]
trait MerchantConnectorAccountUpdateBridge {
async fn get_merchant_connector_account_from_id(
self,
db: &dyn StorageInterface,
merchant_id: &id_type::MerchantId,
merchant_connector_id: &str,
key_store: &domain::MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> RouterResult<domain::MerchantConnectorAccount>;
async fn create_domain_model_from_request(
self,
state: &SessionState,
key_store: domain::MerchantKeyStore,
mca: &domain::MerchantConnectorAccount,
key_manager_state: &KeyManagerState,
merchant_account: &domain::MerchantAccount,
) -> RouterResult<domain::MerchantConnectorAccountUpdate>;
}
#[cfg(all(
feature = "v2",
feature = "merchant_connector_account_v2",
feature = "olap"
))]
#[async_trait::async_trait]
impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnectorUpdate {
async fn get_merchant_connector_account_from_id(
self,
db: &dyn StorageInterface,
_merchant_id: &id_type::MerchantId,
merchant_connector_id: &str,
key_store: &domain::MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> RouterResult<domain::MerchantConnectorAccount> {
db.find_by_merchant_connector_account_id(
key_manager_state,
merchant_connector_id,
key_store,
)
.await
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)
}
async fn create_domain_model_from_request(
self,
state: &SessionState,
key_store: domain::MerchantKeyStore,
mca: &domain::MerchantConnectorAccount,
key_manager_state: &KeyManagerState,
merchant_account: &domain::MerchantAccount,
) -> RouterResult<domain::MerchantConnectorAccountUpdate> {
let payment_methods_enabled = PaymentMethodsEnabled {
payment_methods_enabled: &self.payment_methods_enabled,
};
let payment_methods_enabled = payment_methods_enabled.get_payment_methods_enabled()?;
let frm_configs = self.get_frm_config_as_secret();
let auth = types::ConnectorAuthType::from_secret_value(
self.connector_account_details
.clone()
.unwrap_or(mca.connector_account_details.clone().into_inner()),
)
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
field_name: "connector_account_details".to_string(),
expected_format: "auth_type and api_key".to_string(),
})?;
let metadata = self.metadata.clone().or(mca.metadata.clone());
let connector_name = mca.connector_name.as_ref();
let connector_enum = api_models::enums::Connector::from_str(connector_name)
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "connector",
})
.attach_printable_lazy(|| {
format!("unable to parse connector name {connector_name:?}")
})?;
let connector_auth_type_and_metadata_validation = ConnectorAuthTypeAndMetadataValidation {
connector_name: &connector_enum,
auth_type: &auth,
connector_meta_data: &metadata,
};
connector_auth_type_and_metadata_validation.validate_auth_and_metadata_type()?;
let connector_status_and_disabled_validation = ConnectorStatusAndDisabledValidation {
status: &self.status,
disabled: &self.disabled,
auth: &auth,
current_status: &mca.status,
};
let (connector_status, disabled) =
connector_status_and_disabled_validation.validate_status_and_disabled()?;
let pm_auth_config_validation = PMAuthConfigValidation {
connector_type: &self.connector_type,
pm_auth_config: &self.pm_auth_config,
db: state.store.as_ref(),
merchant_id: merchant_account.get_id(),
profile_id: &mca.profile_id.clone(),
key_store: &key_store,
key_manager_state,
};
pm_auth_config_validation.validate_pm_auth_config().await?;
Ok(storage::MerchantConnectorAccountUpdate::Update {
connector_type: Some(self.connector_type),
connector_label: self.connector_label.clone(),
connector_account_details: self
.connector_account_details
.async_lift(|inner| {
domain_types::encrypt_optional(
key_manager_state,
inner,
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.get_inner().peek(),
)
})
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting data")?,
disabled,
payment_methods_enabled,
metadata: self.metadata,
frm_configs,
connector_webhook_details: match &self.connector_webhook_details {
Some(connector_webhook_details) => connector_webhook_details
.encode_to_value()
.change_context(errors::ApiErrorResponse::InternalServerError)
.map(Some)?
.map(Secret::new),
None => None,
},
applepay_verified_domains: None,
pm_auth_config: self.pm_auth_config,
status: Some(connector_status),
connector_wallets_details: helpers::get_encrypted_apple_pay_connector_wallets_details(
state, &key_store, &metadata,
)
.await?,
})
}
}
#[cfg(all(
any(feature = "v1", feature = "v2", feature = "olap"),
not(feature = "merchant_connector_account_v2")
))]
#[async_trait::async_trait]
impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnectorUpdate {
async fn get_merchant_connector_account_from_id(
self,
db: &dyn StorageInterface,
merchant_id: &id_type::MerchantId,
merchant_connector_id: &str,
key_store: &domain::MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> RouterResult<domain::MerchantConnectorAccount> {
db.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
key_manager_state,
merchant_id,
merchant_connector_id,
key_store,
)
.await
.to_not_found_response(
errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
id: merchant_connector_id.to_string(),
},
)
}
async fn create_domain_model_from_request(
self,
state: &SessionState,
key_store: domain::MerchantKeyStore,
mca: &domain::MerchantConnectorAccount,
key_manager_state: &KeyManagerState,
merchant_account: &domain::MerchantAccount,
) -> RouterResult<domain::MerchantConnectorAccountUpdate> {
let payment_methods_enabled = self.payment_methods_enabled.map(|pm_enabled| {
pm_enabled
.iter()
.flat_map(Encode::encode_to_value)
.map(Secret::new)
.collect::<Vec<pii::SecretSerdeValue>>()
});
let frm_configs = get_frm_config_as_secret(self.frm_configs);
let auth: types::ConnectorAuthType = self
.connector_account_details
.clone()
.unwrap_or(mca.connector_account_details.clone().into_inner())
.parse_value("ConnectorAuthType")
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
field_name: "connector_account_details".to_string(),
expected_format: "auth_type and api_key".to_string(),
})?;
let metadata = self.metadata.clone().or(mca.metadata.clone());
let connector_name = mca.connector_name.as_ref();
let connector_enum = api_models::enums::Connector::from_str(connector_name)
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "connector",
})
.attach_printable_lazy(|| {
format!("unable to parse connector name {connector_name:?}")
})?;
let connector_auth_type_and_metadata_validation = ConnectorAuthTypeAndMetadataValidation {
connector_name: &connector_enum,
auth_type: &auth,
connector_meta_data: &metadata,
};
connector_auth_type_and_metadata_validation.validate_auth_and_metadata_type()?;
let connector_status_and_disabled_validation = ConnectorStatusAndDisabledValidation {
status: &self.status,
disabled: &self.disabled,
auth: &auth,
current_status: &mca.status,
};
let (connector_status, disabled) =
connector_status_and_disabled_validation.validate_status_and_disabled()?;
if self.connector_type != api_enums::ConnectorType::PaymentMethodAuth {
if let Some(val) = self.pm_auth_config.clone() {
validate_pm_auth(
val,
state,
merchant_account.get_id(),
&key_store,
merchant_account.clone(),
&mca.profile_id,
)
.await?;
}
}
Ok(storage::MerchantConnectorAccountUpdate::Update {
connector_type: Some(self.connector_type),
connector_name: None,
merchant_connector_id: None,
connector_label: self.connector_label.clone(),
connector_account_details: self
.connector_account_details
.async_lift(|inner| {
domain_types::encrypt_optional(
key_manager_state,
inner,
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.get_inner().peek(),
)
})
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting data")?,
test_mode: self.test_mode,
disabled,
payment_methods_enabled,
metadata: self.metadata,
frm_configs,
connector_webhook_details: match &self.connector_webhook_details {
Some(connector_webhook_details) => connector_webhook_details
.encode_to_value()
.change_context(errors::ApiErrorResponse::InternalServerError)
.map(Some)?
.map(Secret::new),
None => None,
},
applepay_verified_domains: None,
pm_auth_config: self.pm_auth_config,
status: Some(connector_status),
connector_wallets_details: helpers::get_encrypted_apple_pay_connector_wallets_details(
state, &key_store, &metadata,
)
.await?,
})
}
}
#[cfg(any(feature = "v1", feature = "v2", feature = "olap"))]
#[async_trait::async_trait]
trait MerchantConnectorAccountCreateBridge {
@ -2421,6 +2704,10 @@ pub async fn create_payment_connector(
Ok(service_api::ApplicationResponse::Json(mca_response))
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "merchant_connector_account_v2")
))]
async fn validate_pm_auth(
val: pii::SecretSerdeValue,
state: &SessionState,
@ -2579,164 +2866,27 @@ pub async fn update_payment_connector(
.await
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "merchant_connector_account_v2")
))]
let mca = db
.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
key_manager_state,
let mca = req
.clone()
.get_merchant_connector_account_from_id(
db,
merchant_id,
merchant_connector_id,
&key_store,
)
.await
.to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
id: merchant_connector_id.to_string(),
})?;
#[cfg(all(feature = "v2", feature = "merchant_connector_account_v2"))]
let mca: domain::MerchantConnectorAccount = {
let _ = &merchant_connector_id;
let _ = &req;
let _ = &merchant_account;
todo!()
};
let payment_methods_enabled = req.payment_methods_enabled.map(|pm_enabled| {
pm_enabled
.iter()
.flat_map(Encode::encode_to_value)
.map(Secret::new)
.collect::<Vec<Secret<serde_json::Value>>>()
});
let frm_configs = get_frm_config_as_secret(req.frm_configs);
let auth: types::ConnectorAuthType = req
.connector_account_details
.clone()
.unwrap_or(mca.connector_account_details.clone().into_inner())
.parse_value("ConnectorAuthType")
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
field_name: "connector_account_details".to_string(),
expected_format: "auth_type and api_key".to_string(),
})?;
let metadata = req.metadata.clone().or(mca.metadata.clone());
let connector_name = mca.connector_name.as_ref();
let connector_enum = api_models::enums::Connector::from_str(connector_name)
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "connector",
})
.attach_printable_lazy(|| format!("unable to parse connector name {connector_name:?}"))?;
let connector_auth_type_and_metadata_validation = ConnectorAuthTypeAndMetadataValidation {
connector_name: &connector_enum,
auth_type: &auth,
connector_meta_data: &metadata,
};
connector_auth_type_and_metadata_validation.validate_auth_and_metadata_type()?;
let connector_status_and_disabled_validation = ConnectorStatusAndDisabledValidation {
status: &req.status,
disabled: &req.disabled,
auth: &auth,
current_status: &mca.status,
};
let (connector_status, disabled) =
connector_status_and_disabled_validation.validate_status_and_disabled()?;
if req.connector_type != api_enums::ConnectorType::PaymentMethodAuth {
if let Some(val) = req.pm_auth_config.clone() {
validate_pm_auth(
val,
&state,
merchant_id,
&key_store,
merchant_account,
&mca.profile_id,
key_manager_state,
)
.await?;
}
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "merchant_connector_account_v2")
))]
let payment_connector = storage::MerchantConnectorAccountUpdate::Update {
connector_type: Some(req.connector_type),
connector_name: None,
merchant_connector_id: None,
connector_label: req.connector_label.clone(),
connector_account_details: req
.connector_account_details
.async_lift(|inner| {
domain_types::encrypt_optional(
let payment_connector = req
.clone()
.create_domain_model_from_request(
&state,
key_store.clone(),
&mca,
key_manager_state,
inner,
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.get_inner().peek(),
&merchant_account,
)
})
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting data")?,
test_mode: req.test_mode,
disabled,
payment_methods_enabled,
metadata: req.metadata,
frm_configs,
connector_webhook_details: match &req.connector_webhook_details {
Some(connector_webhook_details) => connector_webhook_details
.encode_to_value()
.change_context(errors::ApiErrorResponse::InternalServerError)
.map(Some)?
.map(Secret::new),
None => None,
},
applepay_verified_domains: None,
pm_auth_config: req.pm_auth_config,
status: Some(connector_status),
connector_wallets_details: helpers::get_encrypted_apple_pay_connector_wallets_details(
&state, &key_store, &metadata,
)
.await?,
};
#[cfg(all(feature = "v2", feature = "merchant_connector_account_v2"))]
let payment_connector = storage::MerchantConnectorAccountUpdate::Update {
connector_type: Some(req.connector_type),
connector_label: req.connector_label.clone(),
connector_account_details: req
.connector_account_details
.async_lift(|inner| {
domain_types::encrypt_optional(
key_manager_state,
inner,
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.get_inner().peek(),
)
})
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting data")?,
disabled,
payment_methods_enabled,
metadata: req.metadata,
frm_configs,
connector_webhook_details: match &req.connector_webhook_details {
Some(connector_webhook_details) => connector_webhook_details
.encode_to_value()
.change_context(errors::ApiErrorResponse::InternalServerError)
.map(Some)?
.map(Secret::new),
None => None,
},
applepay_verified_domains: None,
pm_auth_config: req.pm_auth_config,
status: Some(connector_status),
connector_wallets_details: helpers::get_encrypted_apple_pay_connector_wallets_details(
&state, &key_store, &metadata,
)
.await?,
};
.await?;
// Profile id should always be present
let profile_id = mca

View File

@ -148,19 +148,36 @@ pub async fn update_mca(
.encode_to_value()
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Error while deserializing connector_account_details")?;
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "merchant_connector_account_v2")
))]
let request = MerchantConnectorUpdate {
connector_type: common_enums::ConnectorType::PaymentProcessor,
connector_account_details: Some(Secret::new(connector_auth_json)),
disabled: Some(false),
status: Some(common_enums::ConnectorStatus::Active),
test_mode: None,
connector_label: None,
payment_methods_enabled: None,
metadata: None,
frm_configs: None,
connector_webhook_details: None,
pm_auth_config: None,
test_mode: None,
};
#[cfg(all(feature = "v2", feature = "merchant_connector_account_v2"))]
let request = MerchantConnectorUpdate {
connector_type: common_enums::ConnectorType::PaymentProcessor,
connector_account_details: Some(Secret::new(connector_auth_json)),
disabled: Some(false),
status: Some(common_enums::ConnectorStatus::Active),
connector_label: None,
payment_methods_enabled: None,
metadata: None,
frm_configs: None,
connector_webhook_details: None,
pm_auth_config: None,
merchant_id: merchant_id.clone(),
};
let mca_response =
admin::update_payment_connector(state.clone(), &merchant_id, &connector_id, request)

View File

@ -293,7 +293,7 @@ pub async fn payment_connector_create(
#[cfg(all(feature = "v2", feature = "merchant_connector_account_v2"))]
#[utoipa::path(
post,
path = "/accounts/{account_id}/connectors",
path = "/connector_accounts",
request_body = MerchantConnectorCreate,
responses(
(status = 200, description = "Merchant Connector Created", body = MerchantConnectorResponse),
@ -431,6 +431,10 @@ pub async fn payment_connector_list(
/// Merchant Connector - Update
///
/// To update an existing Merchant Connector. Helpful in enabling / disabling different payment methods and other settings for the connector etc.
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "merchant_connector_account_v2")
))]
#[utoipa::path(
post,
path = "/accounts/{account_id}/connectors/{connector_id}",
@ -478,6 +482,57 @@ pub async fn payment_connector_update(
))
.await
}
/// Merchant Connector - Update
///
/// To update an existing Merchant Connector. Helpful in enabling / disabling different payment methods and other settings for the connector etc.
#[cfg(all(feature = "v2", feature = "merchant_connector_account_v2"))]
#[utoipa::path(
post,
path = "/connector_accounts/{id}",
request_body = MerchantConnectorUpdate,
params(
("id" = i32, Path, description = "The unique identifier for the Merchant Connector")
),
responses(
(status = 200, description = "Merchant Connector Updated", body = MerchantConnectorResponse),
(status = 404, description = "Merchant Connector does not exist in records"),
(status = 401, description = "Unauthorized request")
),
tag = "Merchant Connector Account",
operation_id = "Update a Merchant Connector",
security(("admin_api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsUpdate))]
pub async fn payment_connector_update(
state: web::Data<AppState>,
req: HttpRequest,
path: web::Path<String>,
json_payload: web::Json<api_models::admin::MerchantConnectorUpdate>,
) -> HttpResponse {
let flow = Flow::MerchantConnectorsUpdate;
let id = path.into_inner();
let payload = json_payload.into_inner();
let merchant_id = payload.merchant_id.clone();
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, _, req, _| update_payment_connector(state, &merchant_id, &id, req),
auth::auth_type(
&auth::AdminApiAuth,
&auth::JWTAuthMerchantFromRoute {
merchant_id: merchant_id.clone(),
required_permission: Permission::MerchantConnectorAccountWrite,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
/// Merchant Connector - Delete
///
/// Delete or Detach a Merchant Connector from Merchant Account

View File

@ -1144,8 +1144,9 @@ impl MerchantConnectorAccount {
{
use super::admin::*;
route =
route.service(web::resource("").route(web::post().to(payment_connector_create)));
route = route
.service(web::resource("").route(web::post().to(payment_connector_create)))
.service(web::resource("/{id}").route(web::post().to(payment_connector_update)));
}
route
}