feat(router): Move organization_id to request header from request body for v2 (#6277)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com>
This commit is contained in:
Anurag Thakur
2024-10-25 15:37:13 +05:30
committed by GitHub
parent e36ea184ae
commit aaac9aa97d
10 changed files with 101 additions and 29 deletions

View File

@ -453,6 +453,20 @@
"summary": "Merchant Account - Create", "summary": "Merchant Account - Create",
"description": "Create a new account for a *merchant* and the *merchant* could be a seller or retailer or client who likes to receive and send payments.\n\nBefore creating the merchant account, it is mandatory to create an organization.", "description": "Create a new account for a *merchant* and the *merchant* could be a seller or retailer or client who likes to receive and send payments.\n\nBefore creating the merchant account, it is mandatory to create an organization.",
"operationId": "Create a Merchant Account", "operationId": "Create a Merchant Account",
"parameters": [
{
"name": "X-Organization-Id",
"in": "header",
"description": "Organization ID for which the merchant account has to be created.",
"required": true,
"schema": {
"type": "string"
},
"example": {
"X-Organization-Id": "org_abcdefghijklmnop"
}
}
],
"requestBody": { "requestBody": {
"content": { "content": {
"application/json": { "application/json": {
@ -466,8 +480,7 @@
"primary_contact_person": "John Doe", "primary_contact_person": "John Doe",
"primary_email": "example@company.com" "primary_email": "example@company.com"
}, },
"merchant_name": "Cloth Store", "merchant_name": "Cloth Store"
"organization_id": "org_abcdefghijklmnop"
} }
}, },
"Create a merchant account with metadata": { "Create a merchant account with metadata": {
@ -476,14 +489,12 @@
"metadata": { "metadata": {
"key_1": "John Doe", "key_1": "John Doe",
"key_2": "Trends" "key_2": "Trends"
}, }
"organization_id": "org_abcdefghijklmnop"
} }
}, },
"Create a merchant account with minimal fields": { "Create a merchant account with minimal fields": {
"value": { "value": {
"merchant_name": "Cloth Store", "merchant_name": "Cloth Store"
"organization_id": "org_abcdefghijklmnop"
} }
} }
} }
@ -9385,8 +9396,7 @@
"MerchantAccountCreate": { "MerchantAccountCreate": {
"type": "object", "type": "object",
"required": [ "required": [
"merchant_name", "merchant_name"
"organization_id"
], ],
"properties": { "properties": {
"merchant_name": { "merchant_name": {
@ -9407,13 +9417,6 @@
"type": "object", "type": "object",
"description": "Metadata is useful for storing additional, unstructured information about the merchant account.", "description": "Metadata is useful for storing additional, unstructured information about the merchant account.",
"nullable": true "nullable": true
},
"organization_id": {
"type": "string",
"description": "The id of the organization to which the merchant belongs to. Please use the organization endpoint to create an organization",
"example": "org_q98uSGAYbjEwqs0mJwnz",
"maxLength": 64,
"minLength": 1
} }
}, },
"additionalProperties": false "additionalProperties": false

View File

@ -178,7 +178,8 @@ impl MerchantAccountCreate {
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct MerchantAccountCreate { #[schema(as = MerchantAccountCreate)]
pub struct MerchantAccountCreateWithoutOrgId {
/// Name of the Merchant Account, This will be used as a prefix to generate the id /// Name of the Merchant Account, This will be used as a prefix to generate the id
#[schema(value_type= String, max_length = 64, example = "NewAge Retailer")] #[schema(value_type= String, max_length = 64, example = "NewAge Retailer")]
pub merchant_name: Secret<common_utils::new_type::MerchantName>, pub merchant_name: Secret<common_utils::new_type::MerchantName>,
@ -189,9 +190,17 @@ pub struct MerchantAccountCreate {
/// Metadata is useful for storing additional, unstructured information about the merchant account. /// Metadata is useful for storing additional, unstructured information about the merchant account.
#[schema(value_type = Option<Object>, example = r#"{ "city": "NY", "unit": "245" }"#)] #[schema(value_type = Option<Object>, example = r#"{ "city": "NY", "unit": "245" }"#)]
pub metadata: Option<pii::SecretSerdeValue>, pub metadata: Option<pii::SecretSerdeValue>,
}
/// The id of the organization to which the merchant belongs to. Please use the organization endpoint to create an organization // In v2 the struct used in the API is MerchantAccountCreateWithoutOrgId
#[schema(value_type = String, max_length = 64, min_length = 1, example = "org_q98uSGAYbjEwqs0mJwnz")] // The following struct is only used internally, so we can reuse the common
// part of `create_merchant_account` without duplicating its code for v2
#[cfg(feature = "v2")]
#[derive(Clone, Debug, Serialize)]
pub struct MerchantAccountCreate {
pub merchant_name: Secret<common_utils::new_type::MerchantName>,
pub merchant_details: Option<MerchantDetails>,
pub metadata: Option<pii::SecretSerdeValue>,
pub organization_id: id_type::OrganizationId, pub organization_id: id_type::OrganizationId,
} }

View File

@ -1,3 +1,5 @@
use crate::errors::{CustomResult, ValidationError};
crate::id_type!( crate::id_type!(
OrganizationId, OrganizationId,
"A type for organization_id that can be used for organization ids" "A type for organization_id that can be used for organization ids"
@ -13,3 +15,10 @@ crate::impl_generate_id_id_type!(OrganizationId, "org");
crate::impl_serializable_secret_id_type!(OrganizationId); crate::impl_serializable_secret_id_type!(OrganizationId);
crate::impl_queryable_id_type!(OrganizationId); crate::impl_queryable_id_type!(OrganizationId);
crate::impl_to_sql_from_sql_id_type!(OrganizationId); crate::impl_to_sql_from_sql_id_type!(OrganizationId);
impl OrganizationId {
/// Get an organization id from String
pub fn wrap(org_id: String) -> CustomResult<Self, ValidationError> {
Self::try_from(std::borrow::Cow::from(org_id))
}
}

View File

@ -154,7 +154,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::organization::OrganizationCreateRequest, api_models::organization::OrganizationCreateRequest,
api_models::organization::OrganizationUpdateRequest, api_models::organization::OrganizationUpdateRequest,
api_models::organization::OrganizationResponse, api_models::organization::OrganizationResponse,
api_models::admin::MerchantAccountCreate, api_models::admin::MerchantAccountCreateWithoutOrgId,
api_models::admin::MerchantAccountUpdate, api_models::admin::MerchantAccountUpdate,
api_models::admin::MerchantAccountDeleteResponse, api_models::admin::MerchantAccountDeleteResponse,
api_models::admin::MerchantConnectorDeleteResponse, api_models::admin::MerchantConnectorDeleteResponse,

View File

@ -51,6 +51,13 @@ pub async fn merchant_account_create() {}
#[utoipa::path( #[utoipa::path(
post, post,
path = "/v2/merchant_accounts", path = "/v2/merchant_accounts",
params(
(
"X-Organization-Id" = String, Header,
description = "Organization ID for which the merchant account has to be created.",
example = json!({"X-Organization-Id": "org_abcdefghijklmnop"})
),
),
request_body( request_body(
content = MerchantAccountCreate, content = MerchantAccountCreate,
examples( examples(
@ -58,7 +65,6 @@ pub async fn merchant_account_create() {}
"Create a merchant account with minimal fields" = ( "Create a merchant account with minimal fields" = (
value = json!({ value = json!({
"merchant_name": "Cloth Store", "merchant_name": "Cloth Store",
"organization_id": "org_abcdefghijklmnop"
}) })
) )
), ),
@ -66,7 +72,6 @@ pub async fn merchant_account_create() {}
"Create a merchant account with merchant details" = ( "Create a merchant account with merchant details" = (
value = json!({ value = json!({
"merchant_name": "Cloth Store", "merchant_name": "Cloth Store",
"organization_id": "org_abcdefghijklmnop",
"merchant_details": { "merchant_details": {
"primary_contact_person": "John Doe", "primary_contact_person": "John Doe",
"primary_email": "example@company.com" "primary_email": "example@company.com"
@ -78,7 +83,6 @@ pub async fn merchant_account_create() {}
"Create a merchant account with metadata" = ( "Create a merchant account with metadata" = (
value = json!({ value = json!({
"merchant_name": "Cloth Store", "merchant_name": "Cloth Store",
"organization_id": "org_abcdefghijklmnop",
"metadata": { "metadata": {
"key_1": "John Doe", "key_1": "John Doe",
"key_2": "Trends" "key_2": "Trends"

View File

@ -66,6 +66,7 @@ pub mod headers {
pub const X_API_VERSION: &str = "X-ApiVersion"; pub const X_API_VERSION: &str = "X-ApiVersion";
pub const X_FORWARDED_FOR: &str = "X-Forwarded-For"; pub const X_FORWARDED_FOR: &str = "X-Forwarded-For";
pub const X_MERCHANT_ID: &str = "X-Merchant-Id"; pub const X_MERCHANT_ID: &str = "X-Merchant-Id";
pub const X_ORGANIZATION_ID: &str = "X-Organization-Id";
pub const X_LOGIN: &str = "X-Login"; pub const X_LOGIN: &str = "X-Login";
pub const X_TRANS_KEY: &str = "X-Trans-Key"; pub const X_TRANS_KEY: &str = "X-Trans-Key";
pub const X_VERSION: &str = "X-Version"; pub const X_VERSION: &str = "X-Version";

View File

@ -92,7 +92,7 @@ pub async fn organization_retrieve(
.await .await
} }
#[cfg(feature = "olap")] #[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountCreate))] #[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountCreate))]
pub async fn merchant_account_create( pub async fn merchant_account_create(
state: web::Data<AppState>, state: web::Data<AppState>,
@ -112,6 +112,43 @@ pub async fn merchant_account_create(
.await .await
} }
#[cfg(all(feature = "olap", feature = "v2"))]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountCreate))]
pub async fn merchant_account_create(
state: web::Data<AppState>,
req: HttpRequest,
json_payload: web::Json<api_models::admin::MerchantAccountCreateWithoutOrgId>,
) -> HttpResponse {
let flow = Flow::MerchantsAccountCreate;
let headers = req.headers();
let org_id = match auth::HeaderMapStruct::new(headers).get_organization_id_from_header() {
Ok(org_id) => org_id,
Err(e) => return api::log_and_return_error_response(e),
};
// Converting from MerchantAccountCreateWithoutOrgId to MerchantAccountCreate so we can use the existing
// `create_merchant_account` function for v2 as well
let json_payload = json_payload.into_inner();
let new_request_payload_with_org_id = api_models::admin::MerchantAccountCreate {
merchant_name: json_payload.merchant_name,
merchant_details: json_payload.merchant_details,
metadata: json_payload.metadata,
organization_id: org_id,
};
Box::pin(api::server_wrap(
flow,
state,
&req,
new_request_payload_with_org_id,
|state, _, req, _| create_merchant_account(state, req),
&auth::AdminApiAuth,
api_locking::LockAction::NotApplicable,
))
.await
}
/// Merchant Account - Retrieve /// Merchant Account - Retrieve
/// ///
/// Retrieve a merchant account details. /// Retrieve a merchant account details.

View File

@ -931,7 +931,7 @@ where
} }
/// A helper struct to extract headers from the request /// A helper struct to extract headers from the request
struct HeaderMapStruct<'a> { pub(crate) struct HeaderMapStruct<'a> {
headers: &'a HeaderMap, headers: &'a HeaderMap,
} }
@ -981,6 +981,18 @@ impl<'a> HeaderMapStruct<'a> {
) )
}) })
} }
#[cfg(feature = "v2")]
pub fn get_organization_id_from_header(&self) -> RouterResult<id_type::OrganizationId> {
self.get_mandatory_header_value_by_key(headers::X_ORGANIZATION_ID)
.map(|val| val.to_owned())
.and_then(|organization_id| {
id_type::OrganizationId::wrap(organization_id).change_context(
errors::ApiErrorResponse::InvalidRequestData {
message: format!("`{}` header is invalid", headers::X_ORGANIZATION_ID),
},
)
})
}
} }
/// Get the merchant-id from `x-merchant-id` header /// Get the merchant-id from `x-merchant-id` header

View File

@ -1,7 +1,6 @@
{ {
"ma_create": { "ma_create": {
"merchant_name": "Hyperswitch Seller", "merchant_name": "Hyperswitch Seller"
"organization_id": ""
}, },
"ma_update": { "ma_update": {
"merchant_name": "Hyperswitch" "merchant_name": "Hyperswitch"

View File

@ -173,15 +173,13 @@ Cypress.Commands.add(
.replaceAll(" ", "") .replaceAll(" ", "")
.toLowerCase(); .toLowerCase();
// Update request body
merchantAccountCreateBody.organization_id = organization_id;
cy.request({ cy.request({
method: "POST", method: "POST",
url: url, url: url,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"api-key": api_key, "api-key": api_key,
"X-Organization-Id": organization_id,
}, },
body: merchantAccountCreateBody, body: merchantAccountCreateBody,
failOnStatusCode: false, failOnStatusCode: false,