Skip to content

Commit

Permalink
feat(router): Move organization_id to request header from request bod…
Browse files Browse the repository at this point in the history
…y for v2 (#6277)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Narayan Bhat <[email protected]>
  • Loading branch information
3 people authored Oct 25, 2024
1 parent e36ea18 commit aaac9aa
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 29 deletions.
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)]
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 @@ -92,7 +92,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 @@ -112,6 +112,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)
.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

0 comments on commit aaac9aa

Please sign in to comment.