Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 11 commits into from
Oct 25, 2024
33 changes: 18 additions & 15 deletions api-reference-v2/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,20 @@
"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.",
"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": {
"content": {
"application/json": {
Expand All @@ -466,8 +480,7 @@
"primary_contact_person": "John Doe",
"primary_email": "[email protected]"
},
"merchant_name": "Cloth Store",
"organization_id": "org_abcdefghijklmnop"
"merchant_name": "Cloth Store"
}
},
"Create a merchant account with metadata": {
Expand All @@ -476,14 +489,12 @@
"metadata": {
"key_1": "John Doe",
"key_2": "Trends"
},
"organization_id": "org_abcdefghijklmnop"
}
}
},
"Create a merchant account with minimal fields": {
"value": {
"merchant_name": "Cloth Store",
"organization_id": "org_abcdefghijklmnop"
"merchant_name": "Cloth Store"
}
}
}
Expand Down Expand Up @@ -9385,8 +9396,7 @@
"MerchantAccountCreate": {
"type": "object",
"required": [
"merchant_name",
"organization_id"
"merchant_name"
],
"properties": {
"merchant_name": {
Expand All @@ -9407,13 +9417,6 @@
"type": "object",
"description": "Metadata is useful for storing additional, unstructured information about the merchant account.",
"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
Expand Down
15 changes: 12 additions & 3 deletions crates/api_models/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ impl MerchantAccountCreate {
#[cfg(feature = "v2")]
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MerchantAccountCreate {
#[schema(as = MerchantAccountCreate)]
AnuthaDev marked this conversation as resolved.
Show resolved Hide resolved
pub struct MerchantAccountCreateWithoutOrgId {
/// 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")]
pub merchant_name: Secret<common_utils::new_type::MerchantName>,
Expand All @@ -189,9 +190,17 @@ pub struct MerchantAccountCreate {
/// Metadata is useful for storing additional, unstructured information about the merchant account.
#[schema(value_type = Option<Object>, example = r#"{ "city": "NY", "unit": "245" }"#)]
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
#[schema(value_type = String, max_length = 64, min_length = 1, example = "org_q98uSGAYbjEwqs0mJwnz")]
// In v2 the struct used in the API is MerchantAccountCreateWithoutOrgId
// 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,
}

Expand Down
9 changes: 9 additions & 0 deletions crates/common_utils/src/id_type/organization.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::errors::{CustomResult, ValidationError};

crate::id_type!(
OrganizationId,
"A type for organization_id that can be used for organization ids"
Expand All @@ -13,3 +15,10 @@ crate::impl_generate_id_id_type!(OrganizationId, "org");
crate::impl_serializable_secret_id_type!(OrganizationId);
crate::impl_queryable_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))
}
}
2 changes: 1 addition & 1 deletion crates/openapi/src/openapi_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::organization::OrganizationCreateRequest,
api_models::organization::OrganizationUpdateRequest,
api_models::organization::OrganizationResponse,
api_models::admin::MerchantAccountCreate,
api_models::admin::MerchantAccountCreateWithoutOrgId,
api_models::admin::MerchantAccountUpdate,
api_models::admin::MerchantAccountDeleteResponse,
api_models::admin::MerchantConnectorDeleteResponse,
Expand Down
10 changes: 7 additions & 3 deletions crates/openapi/src/routes/merchant_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,27 @@ pub async fn merchant_account_create() {}
#[utoipa::path(
post,
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(
content = MerchantAccountCreate,
examples(
(
"Create a merchant account with minimal fields" = (
value = json!({
"merchant_name": "Cloth Store",
"organization_id": "org_abcdefghijklmnop"
})
)
),
(
"Create a merchant account with merchant details" = (
value = json!({
"merchant_name": "Cloth Store",
"organization_id": "org_abcdefghijklmnop",
"merchant_details": {
"primary_contact_person": "John Doe",
"primary_email": "[email protected]"
Expand All @@ -78,7 +83,6 @@ pub async fn merchant_account_create() {}
"Create a merchant account with metadata" = (
value = json!({
"merchant_name": "Cloth Store",
"organization_id": "org_abcdefghijklmnop",
"metadata": {
"key_1": "John Doe",
"key_2": "Trends"
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub mod headers {
pub const X_API_VERSION: &str = "X-ApiVersion";
pub const X_FORWARDED_FOR: &str = "X-Forwarded-For";
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_TRANS_KEY: &str = "X-Trans-Key";
pub const X_VERSION: &str = "X-Version";
Expand Down
39 changes: 38 additions & 1 deletion crates/router/src/routes/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub async fn organization_retrieve(
.await
}

#[cfg(feature = "olap")]
#[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountCreate))]
pub async fn merchant_account_create(
state: web::Data<AppState>,
Expand All @@ -115,6 +115,43 @@ pub async fn merchant_account_create(
.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
///
/// Retrieve a merchant account details.
Expand Down
14 changes: 13 additions & 1 deletion crates/router/src/services/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,7 @@ where
}

/// A helper struct to extract headers from the request
struct HeaderMapStruct<'a> {
pub(crate) struct HeaderMapStruct<'a> {
headers: &'a HeaderMap,
}

Expand Down Expand Up @@ -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.into())
.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
Expand Down
3 changes: 1 addition & 2 deletions cypress-tests-v2/cypress/fixtures/merchant_account.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"ma_create": {
"merchant_name": "Hyperswitch Seller",
"organization_id": ""
"merchant_name": "Hyperswitch Seller"
},
"ma_update": {
"merchant_name": "Hyperswitch"
Expand Down
4 changes: 1 addition & 3 deletions cypress-tests-v2/cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,13 @@ Cypress.Commands.add(
.replaceAll(" ", "")
.toLowerCase();

// Update request body
merchantAccountCreateBody.organization_id = organization_id;

cy.request({
method: "POST",
url: url,
headers: {
"Content-Type": "application/json",
"api-key": api_key,
"X-Organization-Id": organization_id,
},
body: merchantAccountCreateBody,
failOnStatusCode: false,
Expand Down
Loading