fix(router): metadata field update in merchant_account and merchant_connector_account (#359)

Co-authored-by: Abhishek Marrivagu <abhi.codes10@gmail.com>
This commit is contained in:
chikke srujan
2023-01-18 15:56:09 +05:30
committed by GitHub
parent 0391f5ef01
commit b5ffff30df
9 changed files with 181 additions and 114 deletions

View File

@ -3,11 +3,10 @@ use masking::{Secret, StrongSecret};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use utoipa::ToSchema; use utoipa::ToSchema;
pub use self::CreateMerchantAccount as MerchantAccountResponse;
use super::payments::AddressDetails; use super::payments::AddressDetails;
use crate::{enums as api_enums, payment_methods}; use crate::{enums as api_enums, payment_methods};
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] #[derive(Clone, Debug, Deserialize, ToSchema)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct CreateMerchantAccount { pub struct CreateMerchantAccount {
/// The identifier for the Merchant Account /// The identifier for the Merchant Account
@ -68,6 +67,69 @@ pub struct CreateMerchantAccount {
pub locker_id: Option<String>, pub locker_id: Option<String>,
} }
#[derive(Clone, Debug, ToSchema, Serialize)]
pub struct MerchantAccountResponse {
/// The identifier for the Merchant Account
#[schema(max_length = 255, example = "y3oqhf46pyzuxjbcn2giaqnb44")]
pub merchant_id: String,
/// Name of the Merchant Account
#[schema(example = "NewAge Retailer")]
pub merchant_name: Option<String>,
/// API key that will be used for server side API access
#[schema(value_type = Option<String>, example = "Ah2354543543523")]
pub api_key: Option<StrongSecret<String>>,
/// The URL to redirect after the completion of the operation
#[schema(max_length = 255, example = "https://www.example.com/success")]
pub return_url: Option<String>,
/// A boolean value to indicate if payment response hash needs to be enabled
#[schema(default = false, example = true)]
pub enable_payment_response_hash: bool,
/// Refers to the Parent Merchant ID if the merchant being created is a sub-merchant
#[schema(max_length = 255, example = "xkkdf909012sdjki2dkh5sdf")]
pub payment_response_hash_key: Option<String>,
/// A boolean value to indicate if redirect to merchant with http post needs to be enabled
#[schema(default = false, example = true)]
pub redirect_to_merchant_with_http_post: bool,
/// Merchant related details
#[schema(value_type = Option<MerchantDetails>)]
pub merchant_details: Option<serde_json::Value>,
/// Webhook related details
#[schema(value_type = Option<WebhookDetails>)]
pub webhook_details: Option<serde_json::Value>,
/// The routing algorithm to be used to process the incoming request from merchant to outgoing payment processor or payment method. The default is 'Custom'
#[schema(value_type = Option<RoutingAlgorithm>, max_length = 255, example = "custom")]
pub routing_algorithm: Option<serde_json::Value>,
/// A boolean value to indicate if the merchant is a sub-merchant under a master or a parent merchant. By default, its value is false.
#[schema(default = false, example = false)]
pub sub_merchants_enabled: Option<bool>,
/// Refers to the Parent Merchant ID if the merchant being created is a sub-merchant
#[schema(max_length = 255, example = "xkkdf909012sdjki2dkh5sdf")]
pub parent_merchant_id: Option<String>,
/// API key that will be used for server side API access
#[schema(example = "AH3423bkjbkjdsfbkj")]
pub publishable_key: Option<String>,
/// 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>, example = r#"{ "city": "NY", "unit": "245" }"#)]
pub metadata: Option<serde_json::Value>,
/// An identifier for the vault used to store payment method information.
#[schema(example = "locker_abc123")]
pub locker_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct MerchantDetails { pub struct MerchantDetails {

View File

@ -1,3 +1,4 @@
use common_utils::ext_traits::ValueExt;
use error_stack::{report, FutureExt, ResultExt}; use error_stack::{report, FutureExt, ResultExt};
use uuid::Uuid; use uuid::Uuid;
@ -12,7 +13,7 @@ use crate::{
storage::{self, MerchantAccount}, storage::{self, MerchantAccount},
transformers::{ForeignInto, ForeignTryInto}, transformers::{ForeignInto, ForeignTryInto},
}, },
utils::{self, OptionExt, ValueExt}, utils::{self, OptionExt},
}; };
#[inline] #[inline]
@ -29,21 +30,23 @@ pub async fn create_merchant_account(
db: &dyn StorageInterface, db: &dyn StorageInterface,
req: api::CreateMerchantAccount, req: api::CreateMerchantAccount,
) -> RouterResponse<api::MerchantAccountResponse> { ) -> RouterResponse<api::MerchantAccountResponse> {
let publishable_key = &format!("pk_{}", create_merchant_api_key()); let publishable_key = Some(format!("pk_{}", create_merchant_api_key()));
let api_key = create_merchant_api_key();
let mut response = req.clone(); let api_key = Some(create_merchant_api_key().into());
response.api_key = Some(api_key.to_owned().into());
response.publishable_key = Some(publishable_key.to_owned()); let merchant_details = Some(
let merchant_details =
utils::Encode::<api::MerchantDetails>::encode_to_value(&req.merchant_details) utils::Encode::<api::MerchantDetails>::encode_to_value(&req.merchant_details)
.change_context(errors::ApiErrorResponse::InvalidDataValue { .change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "merchant_details", field_name: "merchant_details",
})?; })?,
let webhook_details = );
let webhook_details = Some(
utils::Encode::<api::WebhookDetails>::encode_to_value(&req.webhook_details) utils::Encode::<api::WebhookDetails>::encode_to_value(&req.webhook_details)
.change_context(errors::ApiErrorResponse::InvalidDataValue { .change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "webhook details", field_name: "webhook details",
})?; })?,
);
if let Some(ref routing_algorithm) = req.routing_algorithm { if let Some(ref routing_algorithm) = req.routing_algorithm {
let _: api::RoutingAlgorithm = routing_algorithm let _: api::RoutingAlgorithm = routing_algorithm
@ -58,31 +61,36 @@ pub async fn create_merchant_account(
let merchant_account = storage::MerchantAccountNew { let merchant_account = storage::MerchantAccountNew {
merchant_id: req.merchant_id, merchant_id: req.merchant_id,
merchant_name: req.merchant_name, merchant_name: req.merchant_name,
api_key: Some(api_key.to_string().into()), api_key,
merchant_details: Some(merchant_details), merchant_details,
return_url: req.return_url, return_url: req.return_url,
webhook_details: Some(webhook_details), webhook_details,
routing_algorithm: req.routing_algorithm, routing_algorithm: req.routing_algorithm,
sub_merchants_enabled: req.sub_merchants_enabled, sub_merchants_enabled: req.sub_merchants_enabled,
parent_merchant_id: get_parent_merchant( parent_merchant_id: get_parent_merchant(
db, db,
&req.sub_merchants_enabled, req.sub_merchants_enabled,
req.parent_merchant_id, req.parent_merchant_id,
) )
.await?, .await?,
enable_payment_response_hash: req.enable_payment_response_hash, enable_payment_response_hash: req.enable_payment_response_hash,
payment_response_hash_key: req.payment_response_hash_key, payment_response_hash_key: req.payment_response_hash_key,
redirect_to_merchant_with_http_post: req.redirect_to_merchant_with_http_post, redirect_to_merchant_with_http_post: req.redirect_to_merchant_with_http_post,
publishable_key: Some(publishable_key.to_owned()), publishable_key,
locker_id: req.locker_id, locker_id: req.locker_id,
metadata: req.metadata,
}; };
db.insert_merchant(merchant_account) let merchant_account = db
.insert_merchant(merchant_account)
.await .await
.map_err(|error| { .map_err(|error| {
error.to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantAccount) error.to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantAccount)
})?; })?;
Ok(service_api::ApplicationResponse::Json(response))
Ok(service_api::ApplicationResponse::Json(
merchant_account.foreign_into(),
))
} }
pub async fn get_merchant_account( pub async fn get_merchant_account(
@ -95,34 +103,10 @@ pub async fn get_merchant_account(
.map_err(|error| { .map_err(|error| {
error.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound) error.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)
})?; })?;
let merchant_details = merchant_account
.merchant_details Ok(service_api::ApplicationResponse::Json(
.parse_value("MerchantDetails") merchant_account.foreign_into(),
.change_context(errors::ApiErrorResponse::InternalServerError)?; ))
let webhook_details = merchant_account
.webhook_details
.parse_value("WebhookDetails")
.change_context(errors::ApiErrorResponse::InternalServerError)?;
let response = api::MerchantAccountResponse {
merchant_id: req.merchant_id,
merchant_name: merchant_account.merchant_name,
api_key: merchant_account.api_key,
merchant_details,
return_url: merchant_account.return_url,
webhook_details,
routing_algorithm: merchant_account.routing_algorithm,
sub_merchants_enabled: merchant_account.sub_merchants_enabled,
parent_merchant_id: merchant_account.parent_merchant_id,
enable_payment_response_hash: Some(merchant_account.enable_payment_response_hash),
payment_response_hash_key: merchant_account.payment_response_hash_key,
redirect_to_merchant_with_http_post: Some(
merchant_account.redirect_to_merchant_with_http_post,
),
metadata: None,
publishable_key: merchant_account.publishable_key,
locker_id: merchant_account.locker_id,
};
Ok(service_api::ApplicationResponse::Json(response))
} }
pub async fn merchant_account_update( pub async fn merchant_account_update(
@ -159,76 +143,56 @@ pub async fn merchant_account_update(
.attach_printable("Invalid routing algorithm given")?; .attach_printable("Invalid routing algorithm given")?;
} }
let mut response = req.clone();
let encode_error_handler =
|value: &str| format!("Unable to encode to serde_json::Value, {value}");
let updated_merchant_account = storage::MerchantAccountUpdate::Update { let updated_merchant_account = storage::MerchantAccountUpdate::Update {
merchant_id: merchant_id.to_string(), merchant_name: req.merchant_name,
merchant_name: req
.merchant_name merchant_details: req
.or_else(|| merchant_account.merchant_name.to_owned()), .merchant_details
api_key: merchant_account.api_key.clone(), .as_ref()
merchant_details: if req.merchant_details.is_some() { .map(utils::Encode::<api::MerchantDetails>::encode_to_value)
Some( .transpose()
utils::Encode::<api::MerchantDetails>::encode_to_value(&req.merchant_details) .change_context(errors::ApiErrorResponse::InternalServerError)?,
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable_lazy(|| encode_error_handler("MerchantDetails"))?, return_url: req.return_url,
)
} else { webhook_details: req
merchant_account.merchant_details.to_owned() .webhook_details
}, .as_ref()
return_url: req .map(utils::Encode::<api::WebhookDetails>::encode_to_value)
.return_url .transpose()
.or_else(|| merchant_account.return_url.to_owned()), .change_context(errors::ApiErrorResponse::InternalServerError)?,
webhook_details: if req.webhook_details.is_some() {
Some( routing_algorithm: req.routing_algorithm,
utils::Encode::<api::WebhookDetails>::encode_to_value(&req.webhook_details) sub_merchants_enabled: req.sub_merchants_enabled,
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable_lazy(|| encode_error_handler("WebhookDetails"))?,
)
} else {
merchant_account.webhook_details.to_owned()
},
routing_algorithm: req
.routing_algorithm
.or_else(|| merchant_account.routing_algorithm.clone()),
sub_merchants_enabled: req
.sub_merchants_enabled
.or(merchant_account.sub_merchants_enabled),
parent_merchant_id: get_parent_merchant( parent_merchant_id: get_parent_merchant(
db, db,
&req.sub_merchants_enabled req.sub_merchants_enabled
.or(merchant_account.sub_merchants_enabled), .or(merchant_account.sub_merchants_enabled),
req.parent_merchant_id req.parent_merchant_id
.or_else(|| merchant_account.parent_merchant_id.clone()), .or_else(|| merchant_account.parent_merchant_id.clone()),
) )
.await?, .await?,
enable_payment_response_hash: req enable_payment_response_hash: req.enable_payment_response_hash,
.enable_payment_response_hash payment_response_hash_key: req.payment_response_hash_key,
.or(Some(merchant_account.enable_payment_response_hash)), redirect_to_merchant_with_http_post: req.redirect_to_merchant_with_http_post,
payment_response_hash_key: req locker_id: req.locker_id,
.payment_response_hash_key metadata: req.metadata,
.or_else(|| merchant_account.payment_response_hash_key.to_owned()), merchant_id: merchant_account.merchant_id.to_owned(),
redirect_to_merchant_with_http_post: req api_key: None,
.redirect_to_merchant_with_http_post publishable_key: None,
.or(Some(merchant_account.redirect_to_merchant_with_http_post)),
publishable_key: req
.publishable_key
.or_else(|| merchant_account.publishable_key.clone()),
locker_id: req
.locker_id
.or_else(|| merchant_account.locker_id.to_owned()),
}; };
response.merchant_id = merchant_id.to_string();
response.api_key = merchant_account.api_key.to_owned();
db.update_merchant(merchant_account, updated_merchant_account) let response = db
.update_merchant(merchant_account, updated_merchant_account)
.await .await
.change_context(errors::ApiErrorResponse::InternalServerError) .map_err(|error| {
.attach_printable_lazy(|| format!("Failed while updating merchant: {}", merchant_id))?; error.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)
Ok(service_api::ApplicationResponse::Json(response)) })?;
Ok(service_api::ApplicationResponse::Json(
response.foreign_into(),
))
} }
pub async fn merchant_account_delete( pub async fn merchant_account_delete(
@ -250,7 +214,7 @@ pub async fn merchant_account_delete(
async fn get_parent_merchant( async fn get_parent_merchant(
db: &dyn StorageInterface, db: &dyn StorageInterface,
sub_merchants_enabled: &Option<bool>, sub_merchants_enabled: Option<bool>,
parent_merchant: Option<String>, parent_merchant: Option<String>,
) -> RouterResult<Option<String>> { ) -> RouterResult<Option<String>> {
Ok(match sub_merchants_enabled { Ok(match sub_merchants_enabled {
@ -445,12 +409,11 @@ pub async fn update_payment_connector(
connector_type: Some(req.connector_type.foreign_into()), connector_type: Some(req.connector_type.foreign_into()),
connector_name: Some(req.connector_name), connector_name: Some(req.connector_name),
merchant_connector_id: Some(merchant_connector_id), merchant_connector_id: Some(merchant_connector_id),
connector_account_details: req connector_account_details: req.connector_account_details,
.connector_account_details
.or_else(|| Some(Secret::new(mca.connector_account_details.to_owned()))),
payment_methods_enabled, payment_methods_enabled,
test_mode: mca.test_mode, test_mode: mca.test_mode,
disabled: req.disabled.or(mca.disabled), disabled: req.disabled.or(mca.disabled),
metadata: req.metadata,
}; };
let updated_mca = db let updated_mca = db
@ -471,7 +434,7 @@ pub async fn update_payment_connector(
test_mode: updated_mca.test_mode, test_mode: updated_mca.test_mode,
disabled: updated_mca.disabled, disabled: updated_mca.disabled,
payment_methods_enabled: req.payment_methods_enabled, payment_methods_enabled: req.payment_methods_enabled,
metadata: req.metadata, metadata: updated_mca.metadata,
}; };
Ok(service_api::ApplicationResponse::Json(response)) Ok(service_api::ApplicationResponse::Json(response))
} }

View File

@ -143,6 +143,7 @@ impl MerchantAccountInterface for MockDb {
publishable_key: merchant_account.publishable_key, publishable_key: merchant_account.publishable_key,
storage_scheme: enums::MerchantStorageScheme::PostgresOnly, storage_scheme: enums::MerchantStorageScheme::PostgresOnly,
locker_id: merchant_account.locker_id, locker_id: merchant_account.locker_id,
metadata: merchant_account.metadata,
}; };
accounts.push(account.clone()); accounts.push(account.clone());
Ok(account) Ok(account)

View File

@ -1,10 +1,36 @@
pub use api_models::admin::{ pub use api_models::admin::{
CreateMerchantAccount, DeleteMcaResponse, DeleteResponse, MerchantConnectorId, MerchantDetails, CreateMerchantAccount, DeleteMcaResponse, DeleteResponse, MerchantAccountResponse,
MerchantId, PaymentConnectorCreate, PaymentMethods, RoutingAlgorithm, WebhookDetails, MerchantConnectorId, MerchantDetails, MerchantId, PaymentConnectorCreate, PaymentMethods,
RoutingAlgorithm, WebhookDetails,
}; };
use crate::types::{storage, transformers::Foreign};
impl From<Foreign<storage::MerchantAccount>> for Foreign<MerchantAccountResponse> {
fn from(value: Foreign<storage::MerchantAccount>) -> Self {
let item = value.0;
MerchantAccountResponse {
merchant_id: item.merchant_id,
merchant_name: item.merchant_name,
api_key: item.api_key,
return_url: item.return_url,
enable_payment_response_hash: item.enable_payment_response_hash,
payment_response_hash_key: item.payment_response_hash_key,
redirect_to_merchant_with_http_post: item.redirect_to_merchant_with_http_post,
merchant_details: item.merchant_details,
webhook_details: item.webhook_details,
routing_algorithm: item.routing_algorithm,
sub_merchants_enabled: item.sub_merchants_enabled,
parent_merchant_id: item.parent_merchant_id,
publishable_key: item.publishable_key,
metadata: item.metadata,
locker_id: item.locker_id,
}
.into()
}
}
//use serde::{Serialize, Deserialize}; //use serde::{Serialize, Deserialize};
pub use self::CreateMerchantAccount as MerchantAccountResponse;
//use crate::newtype; //use crate::newtype;

View File

@ -21,6 +21,7 @@ pub struct MerchantAccount {
pub publishable_key: Option<String>, pub publishable_key: Option<String>,
pub storage_scheme: storage_enums::MerchantStorageScheme, pub storage_scheme: storage_enums::MerchantStorageScheme,
pub locker_id: Option<String>, pub locker_id: Option<String>,
pub metadata: Option<serde_json::Value>,
pub routing_algorithm: Option<serde_json::Value>, pub routing_algorithm: Option<serde_json::Value>,
} }
@ -40,6 +41,7 @@ pub struct MerchantAccountNew {
pub redirect_to_merchant_with_http_post: Option<bool>, pub redirect_to_merchant_with_http_post: Option<bool>,
pub publishable_key: Option<String>, pub publishable_key: Option<String>,
pub locker_id: Option<String>, pub locker_id: Option<String>,
pub metadata: Option<serde_json::Value>,
pub routing_algorithm: Option<serde_json::Value>, pub routing_algorithm: Option<serde_json::Value>,
} }
@ -59,6 +61,7 @@ pub enum MerchantAccountUpdate {
redirect_to_merchant_with_http_post: Option<bool>, redirect_to_merchant_with_http_post: Option<bool>,
publishable_key: Option<String>, publishable_key: Option<String>,
locker_id: Option<String>, locker_id: Option<String>,
metadata: Option<serde_json::Value>,
routing_algorithm: Option<serde_json::Value>, routing_algorithm: Option<serde_json::Value>,
}, },
} }
@ -79,6 +82,7 @@ pub struct MerchantAccountUpdateInternal {
redirect_to_merchant_with_http_post: Option<bool>, redirect_to_merchant_with_http_post: Option<bool>,
publishable_key: Option<String>, publishable_key: Option<String>,
locker_id: Option<String>, locker_id: Option<String>,
metadata: Option<serde_json::Value>,
routing_algorithm: Option<serde_json::Value>, routing_algorithm: Option<serde_json::Value>,
} }
@ -100,6 +104,7 @@ impl From<MerchantAccountUpdate> for MerchantAccountUpdateInternal {
redirect_to_merchant_with_http_post, redirect_to_merchant_with_http_post,
publishable_key, publishable_key,
locker_id, locker_id,
metadata,
} => Self { } => Self {
merchant_id: Some(merchant_id), merchant_id: Some(merchant_id),
merchant_name, merchant_name,
@ -115,6 +120,7 @@ impl From<MerchantAccountUpdate> for MerchantAccountUpdateInternal {
redirect_to_merchant_with_http_post, redirect_to_merchant_with_http_post,
publishable_key, publishable_key,
locker_id, locker_id,
metadata,
}, },
} }
} }

View File

@ -44,6 +44,7 @@ pub enum MerchantConnectorAccountUpdate {
disabled: Option<bool>, disabled: Option<bool>,
merchant_connector_id: Option<i32>, merchant_connector_id: Option<i32>,
payment_methods_enabled: Option<Vec<serde_json::Value>>, payment_methods_enabled: Option<Vec<serde_json::Value>>,
metadata: Option<serde_json::Value>,
}, },
} }
#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
@ -57,6 +58,7 @@ pub struct MerchantConnectorAccountUpdateInternal {
disabled: Option<bool>, disabled: Option<bool>,
merchant_connector_id: Option<i32>, merchant_connector_id: Option<i32>,
payment_methods_enabled: Option<Vec<serde_json::Value>>, payment_methods_enabled: Option<Vec<serde_json::Value>>,
metadata: Option<serde_json::Value>,
} }
impl From<MerchantConnectorAccountUpdate> for MerchantConnectorAccountUpdateInternal { impl From<MerchantConnectorAccountUpdate> for MerchantConnectorAccountUpdateInternal {
@ -71,6 +73,7 @@ impl From<MerchantConnectorAccountUpdate> for MerchantConnectorAccountUpdateInte
disabled, disabled,
merchant_connector_id, merchant_connector_id,
payment_methods_enabled, payment_methods_enabled,
metadata,
} => Self { } => Self {
merchant_id, merchant_id,
connector_type, connector_type,
@ -80,6 +83,7 @@ impl From<MerchantConnectorAccountUpdate> for MerchantConnectorAccountUpdateInte
disabled, disabled,
merchant_connector_id, merchant_connector_id,
payment_methods_enabled, payment_methods_enabled,
metadata,
}, },
} }
} }

View File

@ -158,6 +158,7 @@ diesel::table! {
publishable_key -> Nullable<Varchar>, publishable_key -> Nullable<Varchar>,
storage_scheme -> MerchantStorageScheme, storage_scheme -> MerchantStorageScheme,
locker_id -> Nullable<Varchar>, locker_id -> Nullable<Varchar>,
metadata -> Nullable<Jsonb>,
routing_algorithm -> Nullable<Json>, routing_algorithm -> Nullable<Json>,
} }
} }

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
ALTER TABLE merchant_account DROP COLUMN metadata;

View File

@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE merchant_account ADD COLUMN metadata JSONB DEFAULT NULL;